|
|
|
|
|
JavaScript閉包在實際開發(fā)中使用并不多,但它卻是面試官必問的問題,因此,作為一個前端開發(fā)人員,閉包是躲不開的一個技術。在本文中,我將從基本術語開始:作用域和詞法作用域,然后,在掌握了基礎知識之后,最終理解閉包。
在開始之前,我建議你不要跳過范圍和詞法范圍這兩部分,這些概念對閉包至關重要,如果你把它們搞好,閉包的概念就變得不言而喻了。
一、適用范圍
當你定義一個變量時,你希望它存在于某些邊界內。例如,result
變量作為內部細節(jié)存在于calculate()
函數(shù)中是有意義的。在calculate()
之外,該result
變量是無用的。
變量的可訪問性由范圍管理,你可以自由訪問在其范圍內定義的變量。但在該范圍之外,該變量是不可訪問的。
在 JavaScript 中,作用域由函數(shù)或代碼塊創(chuàng)建。
讓我們看看范圍如何影響count
變量的可用性。此變量屬于由foo()
創(chuàng)建的范圍:
function foo() {
// 函數(shù)范圍
let count = 0;
console.log(count); // logs 0
}
foo();
console.log(count); // ReferenceError: count is not defined
count
在foo()
范圍內可以自由訪問。
但是,超出foo()
范圍,count
是無法訪問的。如果你嘗試從外部訪問count
,JavaScript 會拋出ReferenceError: count is not defined
。
如果你在函數(shù)或代碼塊內定義了變量,則只能在該函數(shù)或代碼塊內使用此變量。上面的示例演示了這種行為。
現(xiàn)在,讓我們看一個通用的公式:
范圍是一種空間策略,用于規(guī)則變量的可訪問性。
一個直接的屬性出現(xiàn)了——作用域隔離了變量。這很好,因為不同的作用域可以有同名的變量。
你可以在不同的范圍內重用公共變量名稱(count、index、current、value等)而不會發(fā)生沖突。
foo()
和bar()
函數(shù)范圍有它們自己但同名的變量count
:
function foo() {
// "foo" 函數(shù)范圍
let count = 0;
console.log(count); // logs 0
}
function bar() {
// "bar" 函數(shù)范圍
let count = 1;
console.log(count); // logs 1
}
foo();
bar();
來自foo()
和bar()
函數(shù)范圍的變量count
不會發(fā)生沖突。
2、范圍嵌套
讓我們更多地使用范圍,并將一個范圍嵌套到另一個范圍中。例如,函數(shù)innerFunc()
嵌套在外部函數(shù)outerFunc()
中。
兩個函數(shù)作用域如何相互交互?我可以從innerFunc()
范圍內訪問outerFunc()
范圍的變量outerVar
嗎?
讓我們在示例中嘗試一下:
function outerFunc() {
// 外部范圍
let outerVar = 'I am outside!';
function innerFunc() {
// 內部范圍
console.log(outerVar); // => logs "I am outside!"
}
innerFunc();
}
outerFunc();
結果是,outerVar
變量可以在innerFunc()
范圍內訪問。外部范圍的變量可以在內部范圍內訪問。
現(xiàn)在你知道了兩點:
3、詞法范圍
JavaScript 是如何理解innerFunc()
內部的變量outerVar
對應outerFunc()
的變量outerVar
的?
JavaScript 實現(xiàn)了一種名為詞法作用域(或靜態(tài)作用域)的作用域機制。詞法作用域意味著變量的可訪問性由變量在嵌套作用域內的位置決定。
更簡單,詞法作用域意味著在內部作用域內可以訪問外部作用域的變量。
它被稱為詞法(或靜態(tài)),因為引擎(在詞法分析時)僅通過查看 JavaScript 源代碼而不執(zhí)行它來確定范圍的嵌套。
詞法作用域的思想是:
詞法范圍由靜態(tài)確定的外部范圍組成。
例如:
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、閉包
通過前面的介紹,我們知道了詞法范圍允許靜態(tài)訪問外部范圍的變量。距離閉包僅一步之遙!
讓我們再看一下outerFunc()
和innerFunc()
例子:
function outerFunc() {
let outerVar = 'I am outside!';
function innerFunc() {
console.log(outerVar); // => logs "I am outside!"
}
innerFunc();
}
outerFunc();
在innerFunc()
作用域內,從詞法作用域訪問outerVar
變量,這個我們已經(jīng)知道了。
請注意,innerFunc()
調用發(fā)生在其詞法范圍(outerFunc()
的范圍)內。
讓我們做一個改變:innerFunc()
在其詞法范圍之外被調用:在一個函數(shù)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();
現(xiàn)在innerFunc()
在其詞法范圍之外執(zhí)行,但在exec()
函數(shù)范圍內。重要的是:
innerFunc()
仍然可以從其詞法范圍里訪問outerVar
,甚至在其詞法范圍之外執(zhí)行。
換句話說,innerFunc()
從它的詞法范圍中關閉了(也就是捕獲、記?。?code>outerVar變量。
換句話說,innerFunc()
是一個閉包,因為它從其詞法范圍內關閉了outerVar
變量。
現(xiàn)在,你應該了解什么是閉包了:
閉包是一個訪問其詞法范圍的函數(shù),甚至在其詞法范圍之外執(zhí)行。
更簡單地說,閉包是一個函數(shù),它從定義它的地方記住變量,而不管它后來在哪里執(zhí)行。
識別閉包的經(jīng)驗法則:如果在函數(shù)內部看到一個外來變量(未在該函數(shù)內部定義),則該函數(shù)很可能是一個閉包,因為該外來變量已被捕獲。
在前面的代碼片段中,outerVar
是innerFunc()
閉包內的一個外來變量,它從outerFunc()
的作用域捕獲。
總結
本文通過4個方面,從基本術語開始,由淺入深逐步介紹了什么是閉包。通過本文的學習,你應該知道了閉包的基本概念和其固有特點。
相關文章