js閉包

本文首發(fā)于我的博客鸥诽,這是我的github汰瘫,歡迎star。

閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)书斜,創(chuàng)建閉包的常見方式诬辈,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。
??之所以一個(gè)內(nèi)部的函數(shù)可以訪問其外部的變量荐吉,而且在其被返回或是調(diào)用時(shí)還可以訪問焙糟,是因?yàn)檫@個(gè)內(nèi)部函數(shù)的作用域鏈中包含外部函數(shù)的作用域。

知識(shí)儲(chǔ)備

在了解閉包之前样屠,先要熟悉以下幾點(diǎn):
??1. 首先要理解執(zhí)行環(huán)境穿撮,執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)缺脉。
??2. 每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象,環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中悦穿。
??3. 每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境攻礼,當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被推入到一個(gè)環(huán)境棧中咧党。而在函數(shù)執(zhí)行之后秘蛔,棧將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境傍衡。
??4. 當(dāng)某個(gè)函數(shù)被調(diào)用時(shí)深员,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境及其相應(yīng)的作用域鏈。然后使用arguments和其他命名參數(shù)的值來初始化函數(shù)的活動(dòng)對(duì)象蛙埂。在函數(shù)中倦畅,活動(dòng)對(duì)象作為變量對(duì)象使用(作用域鏈?zhǔn)怯擅繉拥淖兞繉?duì)象鏈起來的)。
??5. 在作用域鏈中绣的,外部函數(shù)的活動(dòng)對(duì)象始終處于第二位叠赐,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位,直到作用域鏈終點(diǎn)即全局執(zhí)行環(huán)境屡江。
??6. 作用域鏈的本質(zhì)是一個(gè)指向變量對(duì)象的指針列表芭概,它只引用但不實(shí)際包含變量對(duì)象。


1.一般情況下

不談?wù)撻]包惩嘉,一般的罢洲,從在全局執(zhí)行環(huán)境創(chuàng)建一個(gè)函數(shù)開始。
??在創(chuàng)建一個(gè)函數(shù)時(shí)文黎,會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈惹苗,這個(gè)作用域鏈被保存在函數(shù)內(nèi)部的[[Scope]]
??然后執(zhí)行流進(jìn)入這個(gè)函數(shù)耸峭,函數(shù)的執(zhí)行環(huán)境被壓入環(huán)境棧中桩蓉,此函數(shù)執(zhí)行環(huán)境的活動(dòng)對(duì)象作為變量對(duì)象被創(chuàng)建并推入執(zhí)行環(huán)境作用域鏈的前端。
??對(duì)這個(gè)例子中的函數(shù)而言劳闹,其作用域鏈中包含兩個(gè)變量對(duì)象:本地活動(dòng)對(duì)象和全局變量對(duì)象院究。
??無論在什么時(shí)候在函數(shù)中訪問變量時(shí),會(huì)從作用域鏈搜索變量名本涕。
??一般情況下儡首,函數(shù)執(zhí)行完,局部活動(dòng)對(duì)象就會(huì)被銷毀偏友,內(nèi)存中僅有全局作用域(里邊只有全局執(zhí)行環(huán)境的變量對(duì)象)蔬胯。
??以下面這段代碼為例:

function compare (value1, value2) {         
//創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈,保存在[[Scope]]
    if (value1 < value2) {
//訪問函數(shù)變量時(shí)位他,即在代碼最后一條語句執(zhí)行過程中氛濒,會(huì)從作用域鏈前端開始搜索變量名
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}
var result = compare(5, 10);
//執(zhí)行流進(jìn)入函數(shù)時(shí)产场,compare的執(zhí)行環(huán)境壓入環(huán)境棧
//compare執(zhí)行環(huán)境的活動(dòng)對(duì)象作為變量對(duì)象接到作用域鏈的前端
//函數(shù)執(zhí)行完,compare執(zhí)行環(huán)境彈出棧舞竿,compare活動(dòng)對(duì)象銷毀

如圖京景,作用域鏈從0開始向后查找:


閉包1.1

2.產(chǎn)生閉包的情況下

如下是一個(gè)以屬性名作為參數(shù),按其屬性的值對(duì)數(shù)據(jù)進(jìn)行排序的函數(shù):

function createComparisonFunction(propertyName) {     
    return function(object1,object2){         //返回一個(gè)匿名函數(shù)
        var value1=object1[propertyName];
        var value2=object2[propertyName];
        if(value1<value2){
            return -1;
        } else if (value1>value2){
            return 1;
        } else {
            return 0;
        }
    };
}
var data=[{name:"Zachary",age:28},{name:"Nicholas",age:29}];
data.sort(createComparisonFunction("name"));
console.log(data[0]);           //Object {name: "Nicholas", age: 29}
data.sort(createComparisonFunction("age"));
console.log(data[0]);           //Object {name: "Zachary", age: 28}

createComparisonFunction()函數(shù)和返回的匿名函數(shù)的作用域鏈如下圖所示:

閉包2.1

在匿名函數(shù)從createComparisonFunction()中被返回后骗奖,它的作用域鏈被初始化為包含createComparisonFunction()函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象确徙。這樣,匿名函數(shù)就可以訪問在createComparisonFunction()中定義的所有變量执桌。

更為重要的是:

createComparisonFunction()函數(shù)在執(zhí)行完畢后鄙皇,其他活動(dòng)對(duì)象也不會(huì)被銷毀,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動(dòng)對(duì)象仰挣。
??當(dāng)createComparisonFunction()函數(shù)返回后伴逸,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀,但它的活動(dòng)對(duì)象仍然會(huì)留在內(nèi)存中膘壶;直到匿名函數(shù)被銷毀,createComparisonFunction()的活動(dòng)對(duì)象才會(huì)被銷毀错蝴。

例如以下代碼,返回的匿名函數(shù)被保存在變量compareNames中颓芭,通過將compareNames設(shè)置為null來解除對(duì)匿名函數(shù)的引用顷锰,解除引用之后垃圾回收例程將會(huì)清除該匿名函數(shù),隨之該匿名函數(shù)的作用域鏈也會(huì)被銷毀亡问,則其作用域鏈上的其他作用域也會(huì)安全的銷毀(全局作用域除外)官紫。

var compareNames = createComparisonFunction("name");
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
compareNames = null;

3.經(jīng)典的閉包實(shí)例

在學(xué)習(xí)閉包的時(shí)候我們很容易見到以下代碼:

for(var i=1; i<=5; i++) {         
    setTimeout( function timer() {
        console.log(i);
    }, i*1000);
}

如果不理解閉包,很容易認(rèn)為這段代碼輸出的是1,2,3,4,5玛界,每隔一秒輸出一個(gè)万矾。但是其實(shí)是以每秒一次的頻率輸出56悼吱。因?yàn)檠舆t函數(shù)的回調(diào)會(huì)在循環(huán)執(zhí)行完之后再執(zhí)行(即使setTimeout的第二個(gè)參數(shù)為0慎框,由于事件循環(huán)的機(jī)制,回調(diào)函數(shù)依然會(huì)在循環(huán)結(jié)束后執(zhí)行)后添。

之所以我們錯(cuò)誤的認(rèn)為它會(huì)輸出1~5笨枯,是因?yàn)槲覀冏约杭僭O(shè)每個(gè)迭代在運(yùn)行時(shí)都會(huì)給自己“捕獲”一個(gè)i的副本。但是遇西,根據(jù)作用域的工作原理馅精,實(shí)際情況是盡管循環(huán)中五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在同一個(gè)全局作用域中粱檀,即實(shí)際上只有一個(gè)i洲敢。

下面我們嘗試將其結(jié)果修改為輸出1~5。那么下邊的代碼行不行呢茄蚯?

for(var i=1; i<=5; i++) {         
    (function(){
        setTimeout( function timer() {
            console.log(i);
        }, i*1000);
    })();
}

答案是不行压彭。確實(shí)睦优,上邊的代碼在每個(gè)迭代中都添加了一個(gè)獨(dú)有的作用域,但是這個(gè)作用域是空的壮不,沒有對(duì)于i的定義汗盘,所以每次查找i的時(shí)候還是會(huì)向上查找,找到全局作用域中的i并輸出询一。在循環(huán)執(zhí)行完之后的i6隐孽,所以依然每次輸出6。現(xiàn)在我們?yōu)槊總€(gè)小的作用域添加一個(gè)變量健蕊,其值為循環(huán)時(shí)當(dāng)前的i的值菱阵。

for(var i=1; i<=5; i++) {         
    (function(i){
        setTimeout( function timer() {
            console.log(i);
        }, i*1000);
    })(i);
}

現(xiàn)在的結(jié)果已經(jīng)和理想的情況一樣了。ES6擁有更簡(jiǎn)潔的方法創(chuàng)建一個(gè)封閉的作用域:let绊诲,它會(huì)使一個(gè)塊轉(zhuǎn)換成一個(gè)可以被關(guān)閉的作用域(對(duì)于let定義的變量來說塊就是封閉的)送粱。所以你可以寫成下邊的代碼:

for(let i=1; i<=5; i++) {         
    setTimeout( function timer() {
        console.log(i);
    }, i*1000);
}

4.利用閉包創(chuàng)建模塊

我們看以下代碼:

function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
    function doSomething() {
        console.log( something );
    }
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
}
var foo = CoolModule();
foo.doSomething();               //cool
foo.doAnother();                 //1 ! 2 ! 3

這個(gè)模式就被稱為模塊,最常見的實(shí)現(xiàn)模塊模式的方法通常被稱為模塊暴露掂之,這里展示其變體抗俄。

模塊模式需要具備兩個(gè)必要條件:
??1.必須有外部的封閉函數(shù)(CoolModule()),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)世舰。
??2.封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù)动雹,這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)跟压。


以上就是我在學(xué)習(xí)閉包時(shí)的一些總結(jié)胰蝠,歡迎討論。

參考資料:《JavaScript高級(jí)程序設(shè)計(jì)》
?????《你不知道的JavaScript》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末震蒋,一起剝皮案震驚了整個(gè)濱河市茸塞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌查剖,老刑警劉巖钾虐,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異笋庄,居然都是意外死亡效扫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門直砂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菌仁,“玉大人,你說我怎么就攤上這事静暂〖们穑” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵洽蛀,是天一觀的道長摹迷。 經(jīng)常有香客問我弯院,道長,這世上最難降的妖魔是什么泪掀? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任听绳,我火速辦了婚禮,結(jié)果婚禮上异赫,老公的妹妹穿的比我還像新娘椅挣。我一直安慰自己,他們只是感情好塔拳,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布鼠证。 她就那樣靜靜地躺著,像睡著了一般靠抑。 火紅的嫁衣襯著肌膚如雪量九。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天颂碧,我揣著相機(jī)與錄音荠列,去河邊找鬼。 笑死载城,一個(gè)胖子當(dāng)著我的面吹牛肌似,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诉瓦,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼川队,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了睬澡?” 一聲冷哼從身側(cè)響起固额,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎煞聪,沒想到半個(gè)月后斗躏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡米绕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年瑟捣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了馋艺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栅干。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捐祠,靈堂內(nèi)的尸體忽然破棺而出碱鳞,到底是詐尸還是另有隱情,我是刑警寧澤踱蛀,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布窿给,位于F島的核電站贵白,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏崩泡。R本人自食惡果不足惜禁荒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望角撞。 院中可真熱鬧呛伴,春花似錦、人聲如沸谒所。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劣领。三九已至姐军,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尖淘,已是汗流浹背奕锌。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工本谜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糙麦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓亲桥,卻偏偏與公主長得像梆造,于是被迫代替她去往敵國和親缴守。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 談起閉包,它可是JavaScript兩個(gè)核心技術(shù)之一(異步和閉包),在面試以及實(shí)際應(yīng)用當(dāng)中忽肛,我們都離不開它們村砂,甚至...
    sponing閱讀 690評(píng)論 0 7
  • 閉包(closure)是Javascript語言的一個(gè)難點(diǎn),也是它的特色屹逛,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)础废。 一、變量...
    zock閱讀 1,075評(píng)論 2 6
  • 閉包: 官方”的解釋是:閉包是一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù))罕模,因而這些變量也是該...
    小裁縫sun閱讀 619評(píng)論 0 5
  • 本文首發(fā)于我的博客评腺,這是我的github,歡迎來訪淑掌。 ??閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)蒿讥,創(chuàng)建閉包...
    空_城__閱讀 220評(píng)論 0 0
  • 我喜歡貓咪,不記得是什么時(shí)候開始喜歡上的,然而我現(xiàn)在卻是非常的喜歡貓咪芋绸,高貴的媒殉,可愛的,逗比的摔敛,帥氣的廷蓉。 ...
    言卿閱讀 258評(píng)論 0 0