將閉合想像成您下課後隨身攜帶的背包。背包裡有你在課堂上學到的所有筆記和材料。即使課程結束後,您仍然可以在需要時使用背包中的所有物品。類似地,閉包允許函數保留對其外部作用域的變數和參數的訪問,即使在外部函數完成運行並且這些變數在該函數外部不再可訪問之後也是如此。
上面的解釋是描述閉包的典型方式,但是對於剛接觸 JavaScript 的人來說它適合初學者嗎?並不真地。當我第一次遇到它時,我也覺得很困惑。這就是為什麼我寫這篇文章是為了讓閉包盡可能簡單,讓任何人都能理解。我們將先介紹基礎知識,然後再深入探討這個主題。
要理解什麼是閉包,我們必須簡單了解 JavaScript 中的作用域。範圍是指程式碼不同部分中變數和函數的可訪問性。它決定了我們可以在程式中存取某些變數或函數的位置。
作用域有兩種主要類型:全域作用域和局部作用域。在全域作用域中聲明的變數存在於任何函數或區塊之外,從而使它們可以在整個程式碼中存取。相反,在局部範圍內(例如在函數或區塊內)聲明的變數只能在該特定函數或區塊內存取。下面的程式碼說明了這個解釋。
// GLOBAL SCOPE let myName = "John Doe"; function globalScope() { console.log(myName); } globalScope(); //Output John Doe console.log(myName); // Accessible here as well // LOCAL SCOPE function localScope() { let age = 30; console.log(age); } localScope(); //Output 30 console.log(age); //Output age is not defined (Not Accessible)
然而,JavaScript 使用了一個稱為詞法作用域的概念,這對於理解閉包的工作原理至關重要。詞法作用域意味著變數的可訪問性由編寫程式碼時的結構決定。簡單來說,這就像在說:「如果在函數內聲明變量,則只有該函數及其中的任何內容都可以訪問該變量。」{https://javascript.info/closure}。
為了更清楚地理解這一點,讓我們看看 JavaScript 在幕後是如何運作的。 JavaScript 使用稱為執行上下文的東西,它就像一個保存正在運行的程式碼的容器。它追蹤變數、函數以及目前正在運行的程式碼部分。當腳本啟動時,將建立全域執行上下文 (GEC)。需要注意的是,程式中只有一個全域執行上下文。
上圖代表了程式開始時的全域執行上下文。它由兩個階段組成:創建(或記憶體)階段和執行(或程式碼)階段。在創建階段,變數和函數儲存在記憶體中——變數被初始化為未定義,函數被完全儲存。在執行階段,JavaScript 逐行執行程式碼,為變數賦值並執行函數。
現在我們了解了 JavaScript 如何處理執行上下文和詞法作用域,我們可以看到它如何直接與閉包連結。
當內部函數保留對其外部函數作用域中的變數的存取權時,即使外部函數已完成執行,也會建立 JavaScript 中的閉包。這是可能的,因為內部函數保留了定義它的詞法環境,允許它「記住」並使用外部作用域中的變數。
// GLOBAL SCOPE let myName = "John Doe"; function globalScope() { console.log(myName); } globalScope(); //Output John Doe console.log(myName); // Accessible here as well // LOCAL SCOPE function localScope() { let age = 30; console.log(age); } localScope(); //Output 30 console.log(age); //Output age is not defined (Not Accessible)
這是有關上述程式碼如何運作的指南。每當我們呼叫函數時,JavaScript 引擎都會建立一個特定於該函數的函數執行上下文 (FEC),該函數執行上下文是在全域執行上下文 (GEC) 內建立的。與 GEC 不同,一個程式中可以有多個 FEC。每個 FEC 都會經歷自己的創建和執行階段,並擁有自己的變數和詞法環境。詞法環境使函數能夠從其外部作用域存取變數。
當outerFunction被呼叫時,會建立一個新的FEC,在outerFunction內部,我們定義innerFunction,由於詞法作用域,它可以存取outerVariable。在outerFunction返回後,outerFunction的執行上下文將從呼叫堆疊中刪除,但innerFunction由於閉包而保留對outerVariable的存取。因此,當我們稍後呼叫closureExample()時,即使outerFunction已經完成,它仍然可以記錄outerVariable。
讓我們來看看以下範例:
Let’s look at the example below: function outerFunction() { let outerVariable = 'I am John Doe'; return function innerFunction() { console.log(outerVariable); }; } const closureExample = outerFunction(); closureExample(); // Outputs: "I am John Doe"
你認為這段程式碼的輸出會是什麼?你們中的許多人可能猜到了 5,但這真的是正確的輸出嗎?事實上,不,原因如下。函數 y() 引用變數 a,而不是其初始值。當 z() 被呼叫時,由於返回內部函數之前進行了更新,它會記錄 a 的當前值,即 50。讓我們探討另一個例子:
function x(){ let a = 5 function y(){ console.log(a) } a = 50 return y; } let z = x(); console.log(z) z();
程式碼展示了閉包的威力。即使在最裡面的函數 z() 中,它仍然可以從其父作用域存取變數。如果我們檢查瀏覽器並檢查 Sources 選項卡,我們可以看到 x 和 y 上都形成了閉包,這允許 z() 從其父上下文存取 a 和 b。
閉包在 JavaScript 中提供了多種優勢,特別是在編寫更靈活、模組化和可維護的程式碼時。以下是一些主要優點:
1。回呼函數: 閉包在處理非同步程式設計時非常強大,例如回呼、事件監聽器和 Promise。即使在外部函數完成後,它們也允許回調函數保持對外部函數變數的存取。
// GLOBAL SCOPE let myName = "John Doe"; function globalScope() { console.log(myName); } globalScope(); //Output John Doe console.log(myName); // Accessible here as well // LOCAL SCOPE function localScope() { let age = 30; console.log(age); } localScope(); //Output 30 console.log(age); //Output age is not defined (Not Accessible)
2。模組化和可維護性: 閉包允許開發人員編寫更小、可重複使用的程式碼區塊,從而鼓勵模組化。由於閉包可以在函數呼叫之間保留變量,因此減少了對重複邏輯的需求並提高了可維護性。
3。避免全域變數: 閉包有助於減少對全域變數的需求,從而避免潛在的命名衝突並保持全域命名空間乾淨。透過使用閉包,您可以將資料儲存在函數範圍內而不是全域。
閉包是 JavaScript 中一個強大的概念,它允許函數記住和存取其外部作用域中的變量,甚至在函數執行之後也是如此,從而擴展了函數的功能。此功能在創建更模組化、靈活且高效的程式碼方面發揮著重要作用,特別是在處理非同步任務、回調和事件偵聽器時。雖然閉包一開始看起來很複雜,但掌握它們將使您能夠編寫更複雜和優化的 JavaScript。當您繼續練習時,您將發現閉包如何幫助編寫更乾淨、更易於維護的應用程式。不斷嘗試,很快閉包將成為你的 JavaScript 工具箱的自然組成部分。
以上是JavaScript 閉包的魔力:清晰易懂的指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!