|
|
|
|
|
|
JavaScript 中的閉包是許多人難以理解的概念之一。在接下來的文章中,我將清楚地解釋什么是閉包,并且我將使用簡單的代碼示例來說明這一點。
什么是閉包?
閉包是 JavaScript 中的一項功能,其中內部函數可以訪問外部(封閉)函數的變量——作用域鏈。
閉包具有三個作用域鏈:
對于新手來說,這個定義似乎不好理解。不過沒關系,下面會通過簡單的示例說明,新手也可很快理解它。
真正的閉包是什么?
讓我們看一個 JavaScript 中的簡單閉包示例:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}這里我們有兩個函數:
outer()是具有變量b的外部函數,并返回inner函數。inner()是一個內部函數,它的變量a被調用,并在其函數體內訪問outer()的一個變量b。變量b的作用域僅限于outer函數,變量a的作用域僅限于inner函數。
現在讓我們調用outer()函數,并將結果存儲在一個變量X中。然后我們再次調用outer()函數并將其存儲在變量Y中。
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer(); //outer() 第一次調用
var Y = outer(); //outer() 第二次調用讓我們一步一步地看看outer()函數第一次被調用時會發生什么:
b已創建,其范圍僅限于outer()函數,其值設置為10。return inner查找名為inner的變量,發現該變量inner實際上是一個函數,因此返回整個函數體inner。return語句不執行內部函數, 一個函數僅在后跟()時執行,而是該return語句返回函數的整個主體。]return 語句返回的內容存儲在X中,因此,X將存儲以下內容:outer()執行完畢,現在outer()范圍內的所有變量都不存在了。最后一部分很重要,需要理解。一旦函數完成執行,在函數范圍內定義的任何變量都將不復存在。
在函數內部定義的變量的生命周期就是函數執行的生命周期。
這意味著在console.log(a+b)中,變量b僅在outer()函數執行期間存在。一旦outer函數完成執行,變量b就不再存在。
當函數第二次執行時,函數的變量會被再次創建,直到函數完成執行。
因此,當outer()第二次調用時:
b,其范圍僅限于outer()函數,其值設置為10。return inner返回整個函數體inner。return 語句返回的內容存儲在Y中。outer()執行完畢,現在outer()范圍內的所有變量都不存在了。這里重要的一點是,當outer()第二次調用函數時,b會重新創建變量。此外,當outer()函數第二次完成執行時,這個新變量b再次不復存在。
這是要實現的最重要的一點。函數內部的變量只有在函數運行時才存在,一旦函數執行完畢就不再存在。
現在,讓我們回到我們的代碼示例,看看X和Y。由于outer()函數在執行時返回一個函數,因此變量X和Y是函數。
這可以通過在 JavaScript 代碼中添加以下內容來輕松驗證:
console.log(typeof(X)); //X 是類型函數
console.log(typeof(Y)); //Y 是類型函數
由于變量X和Y是函數,我們可以執行它們。在 JavaScript 中,可以通過()在函數名稱后添加來執行函數,例如X()和Y()。
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
var Y = outer();
// outer()函數執行完畢
X(); // X() 第一次調用
X(); // X() 第二次調用
X(); // X() 第三次調用
Y(); // Y() 第一次調用當我們執行X()和Y()時,我們本質上是在執行inner函數。
讓我們逐步檢查X()第一次執行時會發生什么:
a,并將其值設置為20。a + b,JavaScript 知道a的存在,因為它剛剛創建它。但是,變量b不再存在。由于b是外部函數的一部分,b因此僅在outer()函數執行時存在。由于outer()函數在我們調用X()之前就完成了執行,因此outer函數范圍內的任何變量都不再存在,因此變量b也不再存在。由于 JavaScript 中的閉包,該inner函數可以訪問封閉函數的變量。換句話說,inner函數在執行封閉函數時保留封閉函數的作用域鏈,因此可以訪問封閉函數的變量。
在我們的示例中,inner函數保存了outer()函數執行b=10時的值,并繼續保存(關閉)它。
它現在引用它的作用域鏈,并注意到b在其作用域鏈中確實具有變量的值,因為它在outer函數執行b時將值封閉在閉包中。
因此,JavaScript 知道a=20和b=10,并且可以計算a+b。
你可以通過在上面的示例中添加以下代碼行來驗證這一點:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
console.dir(X); //使用 console.dir() 代替 console.log()在控制臺,你可以展開元素以實際查看閉包元素(如下面倒數第四行所示)。請注意,即使在outer()函數完成執行后, 閉包的值b=10也會保留。

變量 b=10 保存在閉包中
現在讓我們重新回顧一下我們在開始時看到的閉包的定義,看看它現在是否更有意義。
所以內部函數有三個作用域鏈:
aouter函數的變量——變量b進一步了解閉包
為了深入了解閉包,讓我們通過添加三行代碼來擴充示例:
function outer() {
var b = 10;
var c = 100;
function inner() {
var a = 20;
console.log("a= " + a + " b= " + b);
a++;
b++;
}
return inner;
}
var X = outer(); // outer() 第一次被調用
var Y = outer(); // outer() 第二次被調用
//outer()函數執行完畢
X(); // X() 第一次調用
X(); // X() 第二次調用
X(); // X() 第三次調用
Y(); // Y() 第一次調用輸出
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
讓我們一步一步地檢查這段代碼,看看到底發生了什么,看看閉包的實際效果!
var X = outer(); // outer()第一次調用
outer()第一次調用,執行以下步驟:
b,并設置為10;c,并設置為100。b(第一次)和c(第一次)。inner函數并賦給X,變量b作為閉包以b=10包含在inner函數作用域鏈中,因為inner使用了變量b。outer函數完成執行,其所有變量不再存在。變量c不再存在,盡管變量b作為閉包存在于inner中。
var Y= outer(); // outer()第二次調用
b,并設置為10;c,并設置為100;b(第二次)和c(第二次)作為我們的引用。inner函數并賦給Y,變量b作為閉包以b(第二次)=10包含在inner函數作用域鏈中,因為inner使用了變量b。outer函數完成執行,其所有變量不再存在。c(第二次)不再存在,盡管變量b(第二次)作為閉包存在于inner中。現在讓我們看看執行以下代碼行時會發生什么:
X(); // X() 第一次調用
X(); // X() 第二次調用
X(); // X() 第三次調用
Y(); // Y() 第一次調用
X()第一次調用時,
a被創建,并設置為20。a的值=20, b的值來自閉包值,b(第一次), 所以b=10。a和b都遞增1。X()完成執行,其所有內部變量(變量a)不再存在。b(第一次)被保存為閉包,所以b(第一次)繼續存在。X()第二次調用時,
a被重新創建,并設置為20。a任何先前的值不再存在,因為它在X()第一次完成執行時不再存在。a的值=20;b的值取自閉包值b(第一次),還要注意,我們在上一次執行中增加了b的值,所以b=11。a和b再次遞增1。X()完成執行并且它的所有內部變量(變量 a ) 不再存在。b(第一次)隨著閉包繼續存在而被保留。X()第三次調用時,
a被重新創建,并設置為20;a任何先前的值不再存在,因為它在X()第二次完成執行時不再存在。b的值來自閉包值——b(第一次);b的值增加了1, 所以b=12。a和b再次遞增1。X()完成執行,其所有內部變量 (變量a)不再存在。b(第一次)隨著閉包繼續存在而被保留。第一次調用 Y() 時,
a被重新創建,并設置為20;a的值=20, b的值來自閉包值—— b(第二次),所以b=10。a和b均遞增1。Y()完成執行,它的所有內部變量(變量a)不再存在。b(第二次)被保存為閉包,所以b(第二次)繼續存在。結束語
閉包是 JavaScript 中一開始難以掌握的微妙概念之一。但是一旦你理解了它們,你就會意識到事情并沒有那么復雜難懂。
相關文章
