|
|
|
|
|
|
JavaScript閉包在實際開發中使用并不多,但它卻是面試官必問的問題,因此,作為一個前端開發人員,閉包是躲不開的一個技術。在本文中,我將從基本術語開始:作用域和詞法作用域,然后,在掌握了基礎知識之后,最終理解閉包。
在開始之前,我建議你不要跳過范圍和詞法范圍這兩部分,這些概念對閉包至關重要,如果你把它們搞好,閉包的概念就變得不言而喻了。
一、適用范圍
當你定義一個變量時,你希望它存在于某些邊界內。例如,result變量作為內部細節存在于calculate()函數中是有意義的。在calculate()之外,該result變量是無用的。
變量的可訪問性由范圍管理,你可以自由訪問在其范圍內定義的變量。但在該范圍之外,該變量是不可訪問的。
在 JavaScript 中,作用域由函數或代碼塊創建。
讓我們看看范圍如何影響count變量的可用性。此變量屬于由foo()創建的范圍:
function foo() {
// 函數范圍
let count = 0;
console.log(count); // logs 0
}
foo();
console.log(count); // ReferenceError: count is not definedcount在foo()范圍內可以自由訪問。
但是,超出foo()范圍,count是無法訪問的。如果你嘗試從外部訪問count,JavaScript 會拋出ReferenceError: count is not defined。
如果你在函數或代碼塊內定義了變量,則只能在該函數或代碼塊內使用此變量。上面的示例演示了這種行為。

現在,讓我們看一個通用的公式:
范圍是一種空間策略,用于規則變量的可訪問性。
一個直接的屬性出現了——作用域隔離了變量。這很好,因為不同的作用域可以有同名的變量。
你可以在不同的范圍內重用公共變量名稱(count、index、current、value等)而不會發生沖突。
foo()和bar()函數范圍有它們自己但同名的變量count:
function foo() {
// "foo" 函數范圍
let count = 0;
console.log(count); // logs 0
}
function bar() {
// "bar" 函數范圍
let count = 1;
console.log(count); // logs 1
}
foo();
bar();來自foo()和bar()函數范圍的變量count不會發生沖突。
2、范圍嵌套
讓我們更多地使用范圍,并將一個范圍嵌套到另一個范圍中。例如,函數innerFunc()嵌套在外部函數outerFunc()中。
兩個函數作用域如何相互交互?我可以從innerFunc()范圍內訪問outerFunc()范圍的變量outerVar嗎?
讓我們在示例中嘗試一下:
function outerFunc() {
// 外部范圍
let outerVar = 'I am outside!';
function innerFunc() {
// 內部范圍
console.log(outerVar); // => logs "I am outside!"
}
innerFunc();
}
outerFunc();結果是,outerVar變量可以在innerFunc()范圍內訪問。外部范圍的變量可以在內部范圍內訪問。
現在你知道了兩點:
3、詞法范圍
JavaScript 是如何理解innerFunc()內部的變量outerVar對應outerFunc()的變量outerVar的?
JavaScript 實現了一種名為詞法作用域(或靜態作用域)的作用域機制。詞法作用域意味著變量的可訪問性由變量在嵌套作用域內的位置決定。
更簡單,詞法作用域意味著在內部作用域內可以訪問外部作用域的變量。
它被稱為詞法(或靜態),因為引擎(在詞法分析時)僅通過查看 JavaScript 源代碼而不執行它來確定范圍的嵌套。
詞法作用域的思想是:
詞法范圍由靜態確定的外部范圍組成。
例如:
const myGlobal = 0;
function func() {
const myVar = 1;
console.log(myGlobal); // logs "0"
function innerOfFunc() {
const myInnerVar = 2;
console.log(myVar, myGlobal); // logs "1 0"
function innerOfInnerOfFunc() {
console.log(myInnerVar, myVar, myGlobal); // logs "2 1 0"
}
innerOfInnerOfFunc();
}
innerOfFunc();
}
func();
innerOfInnerOfFunc()的詞法范圍由innerOfFunc()、func()和全局范圍(最外層范圍)組成的。在innerOfInnerOfFunc()里面,你可以訪問詞法范圍變量myInnerVar、myVar 和 myGlobal。
innerOfFunc()的詞法范圍由func()和全局范圍組成。你可以在innerOfFunc()里訪問詞法范圍變量myVar和myGlobal。
最后,func()的詞法作用域僅由全局作用域組成。你可以在func()里訪問詞法范圍變量myGlobal。
4、閉包
通過前面的介紹,我們知道了詞法范圍允許靜態訪問外部范圍的變量。距離閉包僅一步之遙!
讓我們再看一下outerFunc()和innerFunc()例子:
function outerFunc() {
let outerVar = 'I am outside!';
function innerFunc() {
console.log(outerVar); // => logs "I am outside!"
}
innerFunc();
}
outerFunc();在innerFunc()作用域內,從詞法作用域訪問outerVar變量,這個我們已經知道了。
請注意,innerFunc()調用發生在其詞法范圍(outerFunc()的范圍)內。
讓我們做一個改變:innerFunc()在其詞法范圍之外被調用:在一個函數exec()中。innerFunc()還能訪問outerVar嗎?
讓我們對代碼片段進行調整:
function outerFunc() {
let outerVar = 'I am outside!';
function innerFunc() {
console.log(outerVar); // => logs "I am outside!"
}
return innerFunc;
}
function exec() {
const myInnerFunc = outerFunc();
myInnerFunc();
}
exec();現在innerFunc()在其詞法范圍之外執行,但在exec()函數范圍內。重要的是:
innerFunc()仍然可以從其詞法范圍里訪問outerVar,甚至在其詞法范圍之外執行。
換句話說,innerFunc() 從它的詞法范圍中關閉了(也就是捕獲、記住)outerVar變量。
換句話說,innerFunc()是一個閉包,因為它從其詞法范圍內關閉了outerVar變量。
現在,你應該了解什么是閉包了:
閉包是一個訪問其詞法范圍的函數,甚至在其詞法范圍之外執行。
更簡單地說,閉包是一個函數,它從定義它的地方記住變量,而不管它后來在哪里執行。
識別閉包的經驗法則:如果在函數內部看到一個外來變量(未在該函數內部定義),則該函數很可能是一個閉包,因為該外來變量已被捕獲。
在前面的代碼片段中,outerVar是innerFunc()閉包內的一個外來變量,它從outerFunc()的作用域捕獲。
總結
本文通過4個方面,從基本術語開始,由淺入深逐步介紹了什么是閉包。通過本文的學習,你應該知道了閉包的基本概念和其固有特點。
相關文章
