|
|
|
|
|
JavaScript 中的閉包是許多人難以理解的概念之一。在接下來(lái)的文章中,我將清楚地解釋什么是閉包,并且我將使用簡(jiǎn)單的代碼示例來(lái)說(shuō)明這一點(diǎn)。
什么是閉包?
閉包是 JavaScript 中的一項(xiàng)功能,其中內(nèi)部函數(shù)可以訪問(wèn)外部(封閉)函數(shù)的變量——作用域鏈。
閉包具有三個(gè)作用域鏈:
對(duì)于新手來(lái)說(shuō),這個(gè)定義似乎不好理解。不過(guò)沒(méi)關(guān)系,下面會(huì)通過(guò)簡(jiǎn)單的示例說(shuō)明,新手也可很快理解它。
真正的閉包是什么?
讓我們看一個(gè) JavaScript 中的簡(jiǎn)單閉包示例:
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
這里我們有兩個(gè)函數(shù):
outer()
是具有變量b
的外部函數(shù),并返回inner
函數(shù)。inner()
是一個(gè)內(nèi)部函數(shù),它的變量a
被調(diào)用,并在其函數(shù)體內(nèi)訪問(wèn)outer()
的一個(gè)變量b
。變量b
的作用域僅限于outer
函數(shù),變量a
的作用域僅限于inner
函數(shù)。
現(xiàn)在讓我們調(diào)用outer()
函數(shù),并將結(jié)果存儲(chǔ)在一個(gè)變量X
中。然后我們?cè)俅握{(diào)用outer()
函數(shù)并將其存儲(chǔ)在變量Y
中。
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer(); //outer() 第一次調(diào)用
var Y = outer(); //outer() 第二次調(diào)用
讓我們一步一步地看看outer()
函數(shù)第一次被調(diào)用時(shí)會(huì)發(fā)生什么:
b
已創(chuàng)建,其范圍僅限于outer()
函數(shù),其值設(shè)置為10。return inner
查找名為inner
的變量,發(fā)現(xiàn)該變量inner
實(shí)際上是一個(gè)函數(shù),因此返回整個(gè)函數(shù)體inner
。return
語(yǔ)句不執(zhí)行內(nèi)部函數(shù), 一個(gè)函數(shù)僅在后跟()
時(shí)執(zhí)行,而是該return
語(yǔ)句返回函數(shù)的整個(gè)主體。]return
語(yǔ)句返回的內(nèi)容存儲(chǔ)在X
中,因此,X
將存儲(chǔ)以下內(nèi)容:outer()
執(zhí)行完畢,現(xiàn)在outer()
范圍內(nèi)的所有變量都不存在了。最后一部分很重要,需要理解。一旦函數(shù)完成執(zhí)行,在函數(shù)范圍內(nèi)定義的任何變量都將不復(fù)存在。
在函數(shù)內(nèi)部定義的變量的生命周期就是函數(shù)執(zhí)行的生命周期。
這意味著在console.log(a+b)
中,變量b
僅在outer()
函數(shù)執(zhí)行期間存在。一旦outer
函數(shù)完成執(zhí)行,變量b
就不再存在。
當(dāng)函數(shù)第二次執(zhí)行時(shí),函數(shù)的變量會(huì)被再次創(chuàng)建,直到函數(shù)完成執(zhí)行。
因此,當(dāng)outer()
第二次調(diào)用時(shí):
b
,其范圍僅限于outer()
函數(shù),其值設(shè)置為10
。return inner
返回整個(gè)函數(shù)體inner
。return
語(yǔ)句返回的內(nèi)容存儲(chǔ)在Y
中。outer()
執(zhí)行完畢,現(xiàn)在outer()
范圍內(nèi)的所有變量都不存在了。這里重要的一點(diǎn)是,當(dāng)outer()
第二次調(diào)用函數(shù)時(shí),b
會(huì)重新創(chuàng)建變量。此外,當(dāng)outer()
函數(shù)第二次完成執(zhí)行時(shí),這個(gè)新變量b
再次不復(fù)存在。
這是要實(shí)現(xiàn)的最重要的一點(diǎn)。函數(shù)內(nèi)部的變量只有在函數(shù)運(yùn)行時(shí)才存在,一旦函數(shù)執(zhí)行完畢就不再存在。
現(xiàn)在,讓我們回到我們的代碼示例,看看X
和Y
。由于outer()
函數(shù)在執(zhí)行時(shí)返回一個(gè)函數(shù),因此變量X
和Y
是函數(shù)。
這可以通過(guò)在 JavaScript 代碼中添加以下內(nèi)容來(lái)輕松驗(yàn)證:
console.log(typeof(X)); //X 是類型函數(shù)
console.log(typeof(Y)); //Y 是類型函數(shù)
由于變量X
和Y
是函數(shù),我們可以執(zhí)行它們。在 JavaScript 中,可以通過(guò)()
在函數(shù)名稱后添加來(lái)執(zhí)行函數(shù),例如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()函數(shù)執(zhí)行完畢
X(); // X() 第一次調(diào)用
X(); // X() 第二次調(diào)用
X(); // X() 第三次調(diào)用
Y(); // Y() 第一次調(diào)用
當(dāng)我們執(zhí)行X()
和Y()
時(shí),我們本質(zhì)上是在執(zhí)行inner
函數(shù)。
讓我們逐步檢查X()
第一次執(zhí)行時(shí)會(huì)發(fā)生什么:
a
,并將其值設(shè)置為20。a + b
,JavaScript 知道a
的存在,因?yàn)樗鼊倓倓?chuàng)建它。但是,變量b
不再存在。由于b
是外部函數(shù)的一部分,b
因此僅在outer()
函數(shù)執(zhí)行時(shí)存在。由于outer()
函數(shù)在我們調(diào)用X()之前就完成了執(zhí)行,因此outer
函數(shù)范圍內(nèi)的任何變量都不再存在,因此變量b
也不再存在。由于 JavaScript 中的閉包,該inner
函數(shù)可以訪問(wèn)封閉函數(shù)的變量。換句話說(shuō),inner
函數(shù)在執(zhí)行封閉函數(shù)時(shí)保留封閉函數(shù)的作用域鏈,因此可以訪問(wèn)封閉函數(shù)的變量。
在我們的示例中,inner
函數(shù)保存了outer()
函數(shù)執(zhí)行b=10
時(shí)的值,并繼續(xù)保存(關(guān)閉)它。
它現(xiàn)在引用它的作用域鏈,并注意到b
在其作用域鏈中確實(shí)具有變量的值,因?yàn)樗?code>outer函數(shù)執(zhí)行b
時(shí)將值封閉在閉包中。
因此,JavaScript 知道a=20
和b=10
,并且可以計(jì)算a+b
。
你可以通過(guò)在上面的示例中添加以下代碼行來(lái)驗(yàn)證這一點(diǎn):
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()
在控制臺(tái),你可以展開(kāi)元素以實(shí)際查看閉包元素(如下面倒數(shù)第四行所示)。請(qǐng)注意,即使在outer()
函數(shù)完成執(zhí)行后, 閉包的值b=10
也會(huì)保留。
變量 b=10 保存在閉包中
現(xiàn)在讓我們重新回顧一下我們?cè)陂_(kāi)始時(shí)看到的閉包的定義,看看它現(xiàn)在是否更有意義。
所以內(nèi)部函數(shù)有三個(gè)作用域鏈:
a
outer
函數(shù)的變量——變量b
進(jìn)一步了解閉包
為了深入了解閉包,讓我們通過(guò)添加三行代碼來(lái)擴(kuò)充示例:
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() 第一次被調(diào)用
var Y = outer(); // outer() 第二次被調(diào)用
//outer()函數(shù)執(zhí)行完畢
X(); // X() 第一次調(diào)用
X(); // X() 第二次調(diào)用
X(); // X() 第三次調(diào)用
Y(); // Y() 第一次調(diào)用
輸出
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
讓我們一步一步地檢查這段代碼,看看到底發(fā)生了什么,看看閉包的實(shí)際效果!
var X = outer(); // outer()第一次調(diào)用
outer()
第一次調(diào)用,執(zhí)行以下步驟:
b
,并設(shè)置為10;c
,并設(shè)置為100。b(第一次)
和c(第一次)
。inner
函數(shù)并賦給X
,變量b
作為閉包以b=10
包含在inner
函數(shù)作用域鏈中,因?yàn)?code>inner使用了變量b
。outer
函數(shù)完成執(zhí)行,其所有變量不再存在。變量c
不再存在,盡管變量b
作為閉包存在于inner
中。
var Y= outer(); // outer()第二次調(diào)用
b
,并設(shè)置為10;c
,并設(shè)置為100;b(第二次
)和c(第二次)
作為我們的引用。inner
函數(shù)并賦給Y
,變量b
作為閉包以b(第二次)=10
包含在inner
函數(shù)作用域鏈中,因?yàn)?code>inner使用了變量b
。outer
函數(shù)完成執(zhí)行,其所有變量不再存在。c(第二次)
不再存在,盡管變量b(第二次)
作為閉包存在于inner
中。現(xiàn)在讓我們看看執(zhí)行以下代碼行時(shí)會(huì)發(fā)生什么:
X(); // X() 第一次調(diào)用
X(); // X() 第二次調(diào)用
X(); // X() 第三次調(diào)用
Y(); // Y() 第一次調(diào)用
X()
第一次調(diào)用時(shí),
a
被創(chuàng)建,并設(shè)置為20。a
的值=20, b
的值來(lái)自閉包值,b(第一次)
, 所以b=10
。a
和b
都遞增1。X()
完成執(zhí)行,其所有內(nèi)部變量(變量a
)不再存在。b(第一次)
被保存為閉包,所以b(第一次)
繼續(xù)存在。X()
第二次調(diào)用時(shí),
a
被重新創(chuàng)建,并設(shè)置為20。a
任何先前的值不再存在,因?yàn)樗?code>X()第一次完成執(zhí)行時(shí)不再存在。a
的值=20;b
的值取自閉包值b(第一次)
,還要注意,我們?cè)谏弦淮螆?zhí)行中增加了b
的值,所以b=11
。a
和b
再次遞增1。X()
完成執(zhí)行并且它的所有內(nèi)部變量(變量 a
) 不再存在。b(第一次)
隨著閉包繼續(xù)存在而被保留。X()
第三次調(diào)用時(shí),
a
被重新創(chuàng)建,并設(shè)置為20;a
任何先前的值不再存在,因?yàn)樗?code>X()第二次完成執(zhí)行時(shí)不再存在。b
的值來(lái)自閉包值——b(第一次)
;b
的值增加了1, 所以b=12
。a
和b
再次遞增1。X()
完成執(zhí)行,其所有內(nèi)部變量 (變量a
)不再存在。b(第一次)
隨著閉包繼續(xù)存在而被保留。第一次調(diào)用 Y()
時(shí),
a
被重新創(chuàng)建,并設(shè)置為20;a
的值=20, b
的值來(lái)自閉包值—— b(第二次)
,所以b=10
。a
和b
均遞增1。Y()
完成執(zhí)行,它的所有內(nèi)部變量(變量a
)不再存在。b(第二次)
被保存為閉包,所以b(第二次)
繼續(xù)存在。結(jié)束語(yǔ)
閉包是 JavaScript 中一開(kāi)始難以掌握的微妙概念之一。但是一旦你理解了它們,你就會(huì)意識(shí)到事情并沒(méi)有那么復(fù)雜難懂。
相關(guān)文章