閉包是JavaScript
開發(fā)人員常常談?wù)摰膯?wèn)題,大家普遍對(duì)閉包的認(rèn)知如下:
模糊的認(rèn)知:閉包是定義在函數(shù)內(nèi)部的函數(shù);
清晰的認(rèn)知:閉包是會(huì)保存它引用到的外部變量的特殊函數(shù);
其實(shí)在JavaScript
語(yǔ)言中,以上
2
種認(rèn)知都是錯(cuò)誤的;為了幫助大家正確地認(rèn)識(shí)閉包,現(xiàn)分享出我對(duì)閉包的研究和理解,如下:
1. 閉包
我對(duì)閉包的定義是:
閉包的標(biāo)準(zhǔn)定義:攜帶外部變量的函數(shù)稱為閉包;
我之所以這樣對(duì)閉包下定義,是因?yàn)檫@個(gè)定義幾乎適用所有語(yǔ)言的閉包,如:Object-C
、
Swift
、
JavaScript
等等;所以我認(rèn)為這是較標(biāo)準(zhǔn)的定義;
對(duì)于JavaScript
中的閉包雖然符合標(biāo)準(zhǔn)定義,但是由于
JavaScript
語(yǔ)言的一些特性,使得
JavaScript
中的閉包的實(shí)現(xiàn)與其它語(yǔ)言(如:
Object-C
、
Swift
)的實(shí)現(xiàn)并不一樣;
很多人都認(rèn)為閉包只會(huì)攜帶它內(nèi)部引用的外部變量,并不會(huì)攜帶沒有引用的外部變量,其實(shí)這是錯(cuò)誤的;可以通過(guò)下面的代碼證明:
function
outFun() {
var outArg1 = "
外部參數(shù)
1";
var outArg2 = "
外部參數(shù)
2";
function
outArg3() {
console.log("
外部參數(shù)
3");
}
/*
定義閉包
* codeStr
:字符串類型的參數(shù),該參數(shù)的值將被當(dāng)作代碼執(zhí)行
* return
: 返回將
codeStr
作為代碼執(zhí)行的結(jié)果;
* */
function
closureFun(codeStr) {
console.log("
閉包引用的變量的值:
",outArg1);
return eval(codeStr); //
返回將
codeStr
作為代碼執(zhí)行的結(jié)果;
}
return closureFun;
}
var getValueOf = outFun(); //
獲取閉包
var arg2Value = getValueOf("outArg2"); //
嘗試獲取閉包內(nèi)沒有引用的變量
outArg2
的值;
console.log(arg2Value); //
輸出結(jié)果為:外部參數(shù)
2
var arg3Value = getValueOf("outArg3"); //
嘗試獲取閉包內(nèi)沒有引用的函數(shù)
outArg3
;
arg3Value(); //
輸出結(jié)果為:外部參數(shù)
3
從示例代碼中的運(yùn)行結(jié)果中可以看出,對(duì)于閉包引用到的外部變量outArg1
和 閉包沒有引用到的變量
outArg2
和函數(shù)
outArg3
,在閉包執(zhí)行時(shí)都能被正確地訪問(wèn)到,所以閉包會(huì)攜帶所有的外部變量(函數(shù)也是變量);
為什么會(huì)這樣呢?若要理解,還需先了解一下作用域鏈的知識(shí),下面是我對(duì)JavaScript
的作用域鏈的理解:
2. 作用域鏈的理解
1.
可以把作用域鏈理解成是一個(gè)棧結(jié)構(gòu);
2.
每個(gè)作用域都有一個(gè)作用域?qū)ο笥糜诒4嬖谠撟饔糜騼?nèi)創(chuàng)建的變量
(
包括函數(shù)
)
,其保存的方式是:在作用域內(nèi)創(chuàng)建的變量會(huì)成為作用域?qū)ο蟮膶傩裕?/span>
3.
作用鏈鏈保存的是各級(jí)作用域?qū)ο蟮囊?,其中最近的作用域的作用域?qū)ο笤谧钋岸耍竭h(yuǎn)的作用域的作用域?qū)ο笤娇亢螅?/span>
4.
全局作用域的作用域?qū)ο笫侨謱?duì)象本身;所以,每個(gè)作用域鏈的最后端都是全局對(duì)象的引用;
5.
在全局作用域內(nèi)創(chuàng)建的變量會(huì)成為全局對(duì)象的屬性的原因:由于
2(
在作用域內(nèi)創(chuàng)建的變量會(huì)成為作用域?qū)ο蟮膶傩?/span>
)
和
4(
全局作用域的作用域?qū)ο笫侨謱?duì)象本身
)
,所以在全局作用域創(chuàng)建的變量會(huì)成為全局對(duì)象的屬性;
6.
函數(shù)的作用域鏈?zhǔn)窃诤瘮?shù)對(duì)象被創(chuàng)建時(shí)(被定義時(shí))創(chuàng)建的;
7.
每當(dāng)函數(shù)被執(zhí)行時(shí),都會(huì)新創(chuàng)建一個(gè)函數(shù)的作用域?qū)ο?,并把該作用域?qū)ο笸频阶饔糜蜴湹淖钋岸耍?/span>
8.
第當(dāng)函數(shù)執(zhí)行結(jié)束時(shí),都會(huì)把函數(shù)的作用域?qū)ο髲脑摵瘮?shù)作用鏈中推出;
3. 閉包的本質(zhì)
其實(shí)閉包攜帶外部變量的機(jī)制并非閉包的特有機(jī)制,它是函數(shù)的作用域鏈的一個(gè)效應(yīng);在JavaScript
中,閉包和普通函數(shù)沒有任何本質(zhì)的區(qū)別,閉包只是函數(shù)在某種使用場(chǎng)景下的一個(gè)名字,就好比兇器只是刀在用于行兇時(shí)的名字;
JavaScript中的閉包能攜帶外部變量的原因是:
JavaScript
的函數(shù)在被創(chuàng)建時(shí)(被定義時(shí))會(huì)生成自己的作用域鏈;該作用域鏈會(huì)保存各級(jí)作用域?qū)ο蟮囊?,所?/span>
JavaScript
的函數(shù)能夠訪問(wèn)其外部的所有變量;
說(shuō)見上文的<
作用域鏈的理解
>
所以,本質(zhì)上,JavaScript
中的閉包攜帶的不是外部變量,而是外部的作用域?qū)ο螅?/span>
來(lái)源:
簡(jiǎn)書