理解JavaScript中的作用域
Aug 31, 2023 pm 08:17 PM范圍,或確定變量所在位置的一組規(guī)則,是任何編程語言的最基本概念之一。事實(shí)上,它是如此基本,以至于我們很容易忘記這些規(guī)則是多么微妙!
準(zhǔn)確理解 JavaScript 引擎如何“思考”作用域?qū)⑹鼓苊饩帉懱嵘赡軐?dǎo)致的常見錯誤,讓您做好準(zhǔn)備專注于閉包,并讓您離永遠(yuǎn)不再編寫錯誤更近一步 再次。
...無論如何,它會幫助您理解提升和關(guān)閉。
在本文中,我們將了解:
- JavaScript 中作用域的基礎(chǔ)知識
- 解釋器如何決定哪些變量屬于哪個作用域
- 吊裝的實(shí)際工作原理
- ES6 關(guān)鍵字
let
和const
如何改變游戲規(guī)則
讓我們深入探討。
如果您有興趣了解有關(guān) ES6 的更多信息以及如何利用語法和功能來改進(jìn)和簡化 JavaScript 代碼,為什么不看看這兩門課程:
詞法范圍
如果您之前編寫過一行 JavaScript,您就會知道定義變量的位置決定了您可以使用的位置> 他們。變量的可見性取決于源代碼的結(jié)構(gòu),這一事實(shí)稱為詞法 范圍。
在 JavaScript 中創(chuàng)建作用域有三種方法:
- 創(chuàng)建函數(shù)。在函數(shù)內(nèi)部聲明的變量僅在該函數(shù)內(nèi)部可見,包括在嵌套函數(shù)中。
-
使用
let
或const
在代碼塊內(nèi)聲明變量。此類聲明僅在塊內(nèi)可見。 -
創(chuàng)建
catch
塊。不管你相信與否,這實(shí)際上確實(shí)創(chuàng)建了一個新的范圍!
"use strict"; var mr_global = "Mr Global"; function foo () { var mrs_local = "Mrs Local"; console.log("I can see " + mr_global + " and " + mrs_local + "."); function bar () { console.log("I can also see " + mr_global + " and " + mrs_local + "."); } } foo(); // Works as expected try { console.log("But /I/ can't see " + mrs_local + "."); } catch (err) { console.log("You just got a " + err + "."); } { let foo = "foo"; const bar = "bar"; console.log("I can use " + foo + bar + " in its block..."); } try { console.log("But not outside of it."); } catch (err) { console.log("You just got another " + err + "."); } // Throws ReferenceError! console.log("Note that " + err + " doesn't exist outside of 'catch'!")
上面的代碼片段演示了所有三種作用域機(jī)制。您可以在 Node 或 Firefox 中運(yùn)行它,但 Chrome 還無法與 let
很好地配合。
我們將詳細(xì)討論其中的每一個。讓我們首先詳細(xì)了解 JavaScript 如何確定哪些變量屬于哪個作用域。
編譯過程:鳥瞰
當(dāng)您運(yùn)行一段 JavaScript 時,會發(fā)生兩件事使其正常工作。
- 首先,編譯您的源代碼。
- 然后,編譯后的代碼將被執(zhí)行。
在編譯步驟期間,JavaScript 引擎:
- 記下所有變量名稱
- 將它們注冊到適當(dāng)?shù)姆秶?/li>
- 為自己的價值觀保留空間
只有在執(zhí)行期間,JavaScript 引擎才真正將變量引用的值設(shè)置為等于其賦值。在那之前,它們是 undefined
。
第 1 步:編譯
// I can use first_name anywhere in this program var first_name = "Peleke"; function popup (first_name) { // I can only use last_name inside of this function var last_name = "Sengstacke"; alert(first_name + ' ' + last_name); } popup(first_name);
讓我們逐步了解一下編譯器的作用。
首先,它讀取行 var first_name = "Peleke"
。接下來,它確定將變量保存到的var first_name = "Peleke"
。接下來,它確定將變量保存到的范圍。因?yàn)槲覀兾挥谀_本的頂層,所以它意識到我們處于全局范圍。然后,它將變量 first_name
保存到全局范圍,并將其值初始化為 undefined
范圍
全局范圍function popup (first_name)
。然后,它將變量 first_name
保存到全局范圍,并將其值初始化為 。
的行。因?yàn)?var last_name = "Sengstacke"
,因此編譯器會將變量 last_name
保存到 </em> class="inline">popup
—不到全局范圍 — 并將其值設(shè)置為 undefined
function
果然,編譯器找到了一個。由于我們函數(shù)的第一行中有
var last_name = "Sengstacke"
,因此編譯器會將變量 last_name
保存到 <em> class="inline">popup
—不到全局范圍 — 并將其值設(shè)置為 。
??由于函數(shù)內(nèi)不再有變量聲明,編譯器將退回到全局作用域。由于不再有變量聲明,因此此階段已完成。?? ??請注意,我們實(shí)際上還沒有運(yùn)行任何東西。????此時編譯器的工作只是確保它知道每個人的名字;它不關(guān)心他們做什么。 ?? ??此時,我們的程序知道:??- 全局范圍內(nèi)有一個名為
first_name
的變量。 - 全局范圍內(nèi)有一個名為
popup
的函數(shù)。 - 在
popup
范圍內(nèi)有一個名為last_name
的變量。 first_name
和last_name
的值均為undefined
。
它并不關(guān)心我們是否在代碼中的其他地方分配了這些變量值。引擎在執(zhí)行時會處理這個問題。
第 2 步:執(zhí)行
在下一步中,引擎再次讀取我們的代碼,但這一次,執(zhí)行它。
首先,它讀取行 var first_name = "Peleke"
。為此,引擎會查找名為 first_name
的變量。由于編譯器已經(jīng)用該名稱注冊了一個變量,引擎會找到它,并將其值設(shè)置為 "Peleke"
。
接下來,它讀取行 function popup (first_name)
。由于我們沒有在這里執(zhí)行該函數(shù),因此引擎不感興趣并跳過它。
最后,它讀取行 popup(first_name)
。由于我們在這里執(zhí)行一個函數(shù),因此引擎:
- 查找
popup
的值 - 查找
first_name
的值 - 將
popup
作為函數(shù)執(zhí)行,并傳遞first_name
的值作為參數(shù)
當(dāng)執(zhí)行 popup
時,它會經(jīng)歷相同的過程,但這次是在函數(shù) popup
內(nèi)。它:
- 查找名為
last_name
的變量 - 將
last_name
的值設(shè)置為等于"Sengstacke"
- 查找
alert
,將其作為函數(shù)執(zhí)行,并以"Peleke Sengstacke"
作為參數(shù)
事實(shí)證明,幕后發(fā)生的事情比我們想象的要多得多!
既然您已經(jīng)了解了 JavaScript 如何讀取和運(yùn)行您編寫的代碼,我們就準(zhǔn)備好解決一些更貼近實(shí)際的問題:提升的工作原理。
顯微鏡下的吊裝
讓我們從一些代碼開始。
bar(); function bar () { if (!foo) { alert(foo + "? This is strange..."); } var foo = "bar"; } broken(); // TypeError! var broken = function () { alert("This alert won't show up!"); }
如果運(yùn)行此代碼,您會注意到三件事:
- 在分配之前,您可以引用
foo
,但其值為undefined
。 - 您可以在定義之前調(diào)用
已損壞的
,但您會收到TypeError
。 - 您可以在定義之前調(diào)用
bar
,它會按需要工作。
提升是指 JavaScript 使我們聲明的所有變量名稱在其作用域內(nèi)的任何地方都可用,包括在我們分配給它們之前 .
代碼段中的三種情況是您在自己的代碼中需要注意的三種情況,因此我們將一一逐步介紹它們。
提升變量聲明
請記住,當(dāng) JavaScript 編譯器讀取像 var foo = "bar"
這樣的行時,它會:
- 將名稱
foo
注冊到最近的范圍 - 將
foo
的值設(shè)置為未定義
我們可以在賦值之前使用 foo
的原因是,當(dāng)引擎查找具有該名稱的變量時,它確實(shí)存在。這就是為什么它不會拋出 ReferenceError
。
相反,它獲取值 undefined
,并嘗試使用該值執(zhí)行您要求的任何操作。通常,這是一個錯誤。
記住這一點(diǎn),我們可能會想象 JavaScript 在我們的函數(shù) bar
中看到的更像是這樣:
function bar () { var foo; // undefined if (!foo) { // !undefined is true, so alert alert(foo + "? This is strange..."); } foo = "bar"; }
如果您愿意的話,這是提升的第一條規(guī)則:變量在其整個范圍內(nèi)都可用,但其值為 undefined
,直到您代碼分配給他們。
常見的 JavaScript 習(xí)慣用法是將所有 var
聲明寫入其作用域的頂部,而不是首次使用它們的位置。用 Doug Crockford 的話來說,這可以幫助您的代碼閱讀更像它運(yùn)行。
仔細(xì)想想,這是有道理的。當(dāng)我們以 JavaScript 讀取代碼的方式編寫代碼時,為什么 bar
的行為方式非常清楚,不是嗎?那么為什么不一直這樣寫呢?
提升函數(shù)表達(dá)式
事實(shí)上,當(dāng)我們在定義之前嘗試執(zhí)行 broken
時,我們得到了 TypeError
,這只是第一條提升規(guī)則的一個特例。
我們定義了一個名為 broken
的變量,編譯器會在全局范圍內(nèi)注冊該變量,并將其設(shè)置為等于 undefined
。當(dāng)我們嘗試運(yùn)行它時,引擎會查找 broken
的值,發(fā)現(xiàn)它是 undefined
,并嘗試將 undefined
作為函數(shù)執(zhí)行.
顯然,undefined
不是一個函數(shù),這就是為什么我們得到 TypeError
!
提升函數(shù)聲明
最后,回想一下,在定義 bar
之前,我們可以調(diào)用它。這是由于第二條提升規(guī)則:當(dāng) JavaScript 編譯器找到函數(shù)聲明時,它會使其名稱和定義在其作用域的頂部可用。再次重寫我們的代碼:
function bar () { if (!foo) { alert(foo + "? This is strange..."); } var foo = "bar"; } var broken; // undefined bar(); // bar is already defined, executes fine broken(); // Can't execute undefined! broken = function () { alert("This alert won't show up!"); }
同樣,當(dāng)您編寫作為JavaScript讀取時,它更有意義,您不覺得嗎?
查看:
- 變量聲明和函數(shù)表達(dá)式的名稱在其整個范圍內(nèi)都可用,但它們的值在賦值之前為
undefined
。 - 函數(shù)聲明的名稱??和定義在其整個范圍內(nèi)都可用,甚至在其定義之前。
現(xiàn)在讓我們來看看兩個工作方式稍有不同的新工具:let
和 const
。
<strong>let</strong>
、<strong></strong>const
和臨時死區(qū)強(qiáng)><強(qiáng)>強(qiáng)>
與 var
聲明不同,使用 let
和 const
聲明的變量不?被編譯器提升。
至少,不完全是。
還記得我們?nèi)绾握{(diào)用 已損壞的
,但卻因?yàn)閲L試執(zhí)行 undefined
而收到 TypeError
嗎?如果我們使用 let
定義 broken
,我們就會得到 ReferenceError
,而不是:
"use strict"; // You have to "use strict" to try this in Node broken(); // ReferenceError! let broken = function () { alert("This alert won't show up!"); }
當(dāng) JavaScript 編譯器在第一遍中將變量注冊到其作用域時,它對待 let
和 const
的方式與處理 var
的方式不同。
當(dāng)它找到 var
聲明時,我們將該變量的名稱注冊到其范圍,并立即將其值初始化為 undefined
。
但是,使用 let
,編譯器會將變量注冊到其作用域,但不會初始化其值為 undefined
。相反,它會使變量保持未初始化狀態(tài),直到引擎執(zhí)行您的賦值語句。訪問未初始化變量的值會拋出 ReferenceError
,這解釋了為什么上面的代碼片段在運(yùn)行時會拋出異常。
let
聲明和賦值語句的頂部開頭之間的空間稱為臨時死區(qū)。該名稱源自以下事實(shí):即使引擎知道名為 foo
的變量(位于 bar
范圍的頂部),該變量是“死的”,因?yàn)樗鼪]有值。
...還因?yàn)槿绻鷩L試盡早使用它,它會殺死您的程序。
const
關(guān)鍵字的工作方式與 let
相同,但有兩個主要區(qū)別:
- 使用
const
聲明時,必須分配一個值。 - 您不能為使用
const
聲明的變量重新賦值。
這可以保證 const
將始終擁有您最初分配給它的值。
// This is legal const React = require('react'); // This is totally not legal const crypto; crypto = require('crypto');
塊范圍
let
和 const
與 var
在另一方面有所不同:它們的范圍大小。
當(dāng)您使用 var
聲明變量時,它在作用域鏈的盡可能高的位置可見 - 通常是在最近的函數(shù)聲明的頂部,或者在全局范圍,如果您在頂層聲明它。
但是,當(dāng)您使用 let
或 const
聲明變量時,它會盡可能本地可見 -僅 >?在最近的街區(qū)內(nèi)。
塊是由大括號分隔的一段代碼,如 if
/else
塊、for
?循環(huán),以及顯式“阻止”的代碼塊,如本段代碼所示。
"use strict"; { let foo = "foo"; if (foo) { const bar = "bar"; var foobar = foo + bar; console.log("I can see " + bar + " in this bloc."); } try { console.log("I can see " + foo + " in this block, but not " + bar + "."); } catch (err) { console.log("You got a " + err + "."); } } try { console.log( foo + bar ); // Throws because of 'foo', but both are undefined } catch (err) { console.log( "You just got a " + err + "."); } console.log( foobar ); // Works fine
如果您在塊內(nèi)使用 const
或 let
聲明變量,則該變量僅在塊內(nèi)可見,且僅< /em> 分配后。
但是,使用 var
聲明的變量在盡可能遠(yuǎn)的地方可見 - 在本例中是在全局范圍內(nèi)。
如果您對 let
和 const
的具體細(xì)節(jié)感興趣,請查看 Rauschmayer 博士在《探索 ES6:變量和范圍》中對它們的介紹,并查看有關(guān)它們的 MDN 文檔。
詞法 <strong>this</strong>
和箭頭函數(shù)
從表面上看,this
似乎與范圍沒有太大關(guān)系。事實(shí)上,JavaScript 并沒有根據(jù)我們在這里討論的范圍規(guī)則來解析 this
的含義。
至少,通常不會。眾所周知,JavaScript 不會根據(jù)您使用該關(guān)鍵字的位置來解析 this
關(guān)鍵字的含義:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak : function speak () { this.languages.forEach(function(language) { console.log(this.name + " speaks " + language + "."); }) } }; foo.speak();
我們大多數(shù)人都認(rèn)為 this
表示 foo
在 forEach
循環(huán)內(nèi),因?yàn)檫@就是它在循環(huán)之外的含義。換句話說,我們期望 JavaScript 能夠解析 this
詞法的含義。
但事實(shí)并非如此。
相反,它會在您定義的每個函數(shù)中創(chuàng)建一個新 this
,并根據(jù)您如何調(diào)用該函數(shù)來決定其含義 -不是您定義它的位置。
第一點(diǎn)類似于在子作用域中重新定義任何變量的情況:
function foo () { var bar = "bar"; function baz () { // Reusing variable names like this is called "shadowing" var bar = "BAR"; console.log(bar); // BAR } baz(); } foo(); // BAR
將 bar
替換為 this
,整個事情應(yīng)該立即清楚!
傳統(tǒng)上,要讓 this
按照我們期望的普通舊詞法范圍變量的方式工作,需要以下兩種解決方法之一:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak_self : function speak_s () { var self = this; self.languages.forEach(function(language) { console.log(self.name + " speaks " + language + "."); }) }, speak_bound : function speak_b () { this.languages.forEach(function(language) { console.log(this.name + " speaks " + language + "."); }.bind(foo)); // More commonly:.bind(this); } };
在 speak_self
中,我們將 this
的含義保存到變量 self
中,并使用該變量來得到我們想要的參考。在 speak_bound
中,我們使用 bind
來永久將 this
指向給定對象。
ES2015 為我們帶來了一種新的選擇:箭頭函數(shù)。
與“普通”函數(shù)不同,箭頭函數(shù)不會通過設(shè)置自己的值來隱藏其父作用域的 this
值。相反,他們從詞匯上解析其含義。
換句話說,如果您在箭頭函數(shù)中使用 this
,JavaScript 會像查找任何其他變量一樣查找其值。
首先,它檢查本地范圍內(nèi)的 this
值。由于箭頭函數(shù)沒有設(shè)置一個,因此它不會找到一個。接下來,它檢查 this
值的父范圍。如果找到,它將使用它。
這讓我們可以像這樣重寫上面的代碼:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak : function speak () { this.languages.forEach((language) => { console.log(this.name + " speaks " + language + "."); }) } };???
如果您想了解有關(guān)箭頭函數(shù)的更多詳細(xì)信息,請查看 Envato Tuts+ 講師 Dan Wellman 的有關(guān) JavaScript ES6 基礎(chǔ)知識的精彩課程,以及有關(guān)箭頭函數(shù)的 MDN 文檔。
結(jié)論
到目前為止,我們已經(jīng)了解了很多內(nèi)容!在本文中,您了解到:
- 變量在編譯期間注冊到其作用域,并在執(zhí)行期間與其賦值相關(guān)聯(lián)。
- 在賦值之前引用使用?
let
或 class="inline">const 聲明的變量會引發(fā)ReferenceError
,并且此類變量是范圍到最近的塊。 - 箭頭函數(shù) 允許我們實(shí)現(xiàn)
this
的詞法綁定,并繞過傳統(tǒng)的動態(tài)綁定。
您還了解了提升的兩條規(guī)則:
-
第一條提升規(guī)則:函數(shù)表達(dá)式和
var
聲明在其定義的整個范圍內(nèi)都可用,但其值為undefined
?直到您的賦值語句執(zhí)行。 - 第二條提升規(guī)則:函數(shù)聲明的名稱及其主體在定義它們的范圍內(nèi)可用。
下一步最好是利用 JavaScript 作用域的新知識來理解閉包。為此,請查看 Kyle Simpson 的 Scopes & Closures。
最后,關(guān)于 this
有很多話要說,我無法在此介紹。如果該關(guān)鍵字看起來仍然像是黑魔法,請查看此和對象原型來了解它。
與此同時,利用您所學(xué)到的知識并減少編寫錯誤!
學(xué)習(xí) JavaScript:完整指南
我們構(gòu)建了一個完整的指南來幫助您學(xué)習(xí) JavaScript,無論您是剛剛開始作為 Web 開發(fā)人員還是想探索更高級的主題。
以上是理解JavaScript中的作用域的詳細(xì)內(nèi)容。更多信息請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣服圖片

Undresser.AI Undress
人工智能驅(qū)動的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover
用于從照片中去除衣服的在線人工智能工具。

Clothoff.io
AI脫衣機(jī)

Video Face Swap
使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的代碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級代碼編輯軟件(SublimeText3)

WordPress導(dǎo)致服務(wù)器CPU使用率飆升的主要原因包括插件問題、數(shù)據(jù)庫查詢效率低、主題代碼質(zhì)量差或流量激增。1.首先通過top、htop或控制面板工具確認(rèn)是否為WordPress引起的高負(fù)載;2.進(jìn)入故障排查模式逐步啟用插件排查性能瓶頸,使用QueryMonitor分析插件執(zhí)行情況并刪除或替換低效插件;3.安裝緩存插件、清理冗余數(shù)據(jù)、分析慢查詢?nèi)罩疽詢?yōu)化數(shù)據(jù)庫;4.檢查主題是否存在過度加載內(nèi)容、復(fù)雜查詢或缺乏緩存機(jī)制等問題,建議用標(biāo)準(zhǔn)主題測試對比并優(yōu)化代碼邏輯。按照上述步驟逐一排查可定位并解

MinifyingJavaScript文件可通過刪除空白、注釋和無用代碼來提升WordPress網(wǎng)站加載速度。1.使用支持合并壓縮的緩存插件如W3TotalCache,在“Minify”選項(xiàng)中啟用并選擇壓縮模式;2.使用專用壓縮插件如FastVelocityMinify,提供更精細(xì)控制;3.手動壓縮JS文件并通過FTP上傳,適用于熟悉開發(fā)工具的用戶。注意部分主題或插件腳本可能與壓縮功能沖突,啟用后需徹底測試網(wǎng)站功能。

防止評論垃圾信息最有效的方式是通過程序化手段自動識別并攔截。1.使用驗(yàn)證碼機(jī)制(如GooglereCAPTCHA或hCaptcha)可有效區(qū)分人類與機(jī)器人,尤其適合公眾網(wǎng)站;2.設(shè)置隱藏字段(Honeypot技術(shù)),利用機(jī)器人自動填寫特性識別垃圾評論,不影響用戶體驗(yàn);3.檢查評論內(nèi)容關(guān)鍵詞黑名單,通過敏感詞匹配過濾垃圾信息,需注意避免誤判;4.判斷評論頻率與來源IP,限制單位時間內(nèi)的提交次數(shù)并建立黑名單;5.使用第三方反垃圾服務(wù)(如Akismet、Cloudflare)提升識別準(zhǔn)確性。可根據(jù)網(wǎng)站

在開發(fā)Gutenberg塊時,正確enqueue資產(chǎn)的方法包括:1.使用register_block_type指定editor_script、editor_style和style的路徑;2.在functions.php或插件中通過wp_register_script和wp_register_style注冊資源,并設(shè)置正確的依賴和版本;3.配置構(gòu)建工具輸出合適的模塊格式,并確保路徑一致;4.通過add_theme_support或enqueue_block_assets控制前端樣式的加載邏輯,確保

要添加自定義用戶字段需根據(jù)平臺選擇擴(kuò)展方式并注意數(shù)據(jù)驗(yàn)證與權(quán)限控制。常見做法包括:1.利用數(shù)據(jù)庫額外表或鍵值對結(jié)構(gòu)存儲信息;2.在前端加入輸入框并與后端集成;3.對敏感數(shù)據(jù)進(jìn)行格式校驗(yàn)和訪問權(quán)限限制;4.更新接口及模板以支持新字段展示與編輯,同時兼顧移動端適配和用戶體驗(yàn)。

在WordPress中添加自定義重寫規(guī)則的關(guān)鍵在于使用add_rewrite_rule函數(shù)并確保規(guī)則正確生效。1.使用add_rewrite_rule注冊規(guī)則,格式為add_rewrite_rule($regex,$redirect,$after),其中$regex是正則表達(dá)式匹配URL,$redirect指定實(shí)際查詢,$after控制規(guī)則位置;2.需通過add_filter添加自定義查詢變量;3.修改后必須刷新固定鏈接設(shè)置;4.建議將規(guī)則放在'top'以避免沖突;5.可借助插件查看當(dāng)前規(guī)則便于

robots.txt對WordPress網(wǎng)站的SEO至關(guān)重要,能引導(dǎo)搜索引擎抓取行為,避免重復(fù)內(nèi)容并提升效率。1.屏蔽如/wp-admin/、/wp-includes/等系統(tǒng)路徑,但避免誤封/uploads/目錄;2.添加Sitemap路徑如Sitemap:https://yourdomain.com/sitemap.xml以幫助搜索引擎快速發(fā)現(xiàn)站點(diǎn)地圖;3.限制/page/和帶參數(shù)的URL以減少爬蟲浪費(fèi),但需注意勿封重要?dú)w檔頁;4.避免常見錯誤如誤封全站、緩存插件影響更新及忽略移動端與子域名配

1.使用性能分析插件可快速定位問題,如QueryMonitor可查看數(shù)據(jù)庫查詢次數(shù)與PHP錯誤,BlackboxProfiler生成函數(shù)執(zhí)行報告,NewRelic提供服務(wù)器級分析;2.分析PHP執(zhí)行性能需檢查耗時函數(shù)、調(diào)試工具使用及內(nèi)存分配情況,如Xdebug生成火焰圖輔助優(yōu)化;3.監(jiān)控數(shù)據(jù)庫查詢效率可通過慢查詢?nèi)罩九c索引檢查,QueryMonitor能列出所有SQL并按時間排序;4.結(jié)合GooglePageSpeedInsights、GTmetrix與WebPageTest等外部工具評估前端加
