js閉包

理論上的定義

MDN 對閉包的定義為:

閉包是指那些能夠訪問自由變量的函數(shù)塔猾。

那什么是自由變量呢莺琳?

自由變量是指在函數(shù)中使用的茂契,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量饮亏。

由此,我們可以看出閉包共有兩部分組成:

閉包 = 函數(shù) + 函數(shù)能夠訪問的自由變量

舉個例子:

var a = 1;
function foo() {
  console.log(a);
}
foo();

foo 函數(shù)可以訪問變量 a忠藤,但是 a 既不是 foo 函數(shù)的局部變量挟伙,也不是 foo 函數(shù)的參數(shù),所以 a 就是自由變量模孩。
那么尖阔,函數(shù) foo + foo 函數(shù)訪問的自由變量 a 不就是構成了一個閉包嘛……
所以在《JavaScript權威指南》中就講到:從技術的角度講贮缅,所有的JavaScript函數(shù)都是閉包。
別急這是理論上的閉包介却,我們還有一個實踐角度上的閉包谴供,讓我們看看湯姆大叔翻譯的關于閉包的文章中的定義:

實踐角度上的閉包

ECMAScript中,閉包指的是:
1.從理論角度:所有的函數(shù)齿坷。因為它們都在創(chuàng)建的時候就將上層上下文的數(shù)據(jù)保存起來了桂肌。哪怕是簡單的全局變量也是如此,因為函數(shù)中訪問全局變量就相當于是在訪問自由變量胃夏,這個時候使用最外層的作用域轴或。
2.從實踐角度:以下函數(shù)才算是閉包:
即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在(比如仰禀,內(nèi)部函數(shù)從父函數(shù)中返回)
在代碼中引用了自由變量

分析

舉個例子:

var scope = "global scope";
function checkscope () {
  var scope = "local scope";
  function f() {
    return scope;
  }
  return f;
}
var foo = checkscope();
foo();

首先我們要分析一下這段代碼中執(zhí)行上下文棧和執(zhí)行上下文的變化情況蚕愤。

  • 進入全局代碼答恶,創(chuàng)建全局執(zhí)行上下文,全局執(zhí)行上下文壓入執(zhí)行上下文棧
  • 全局執(zhí)行上下文初始化
  • 執(zhí)行 checkscope 函數(shù)萍诱,創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文悬嗓,checkscope 執(zhí)行上下文被壓入執(zhí)行上下文棧
  • checkscope 執(zhí)行上下文初始化,創(chuàng)建變量對象裕坊、作用域鏈包竹、this等
  • checkscope 函數(shù)執(zhí)行完畢,checkscope 執(zhí)行上下文從執(zhí)行上下文棧中彈出
  • 執(zhí)行 f 函數(shù)籍凝,創(chuàng)建 f 函數(shù)執(zhí)行上下文周瞎,f 執(zhí)行上下文被壓入執(zhí)行上下文棧
  • f 執(zhí)行上下文初始化,創(chuàng)建變量對象饵蒂、作用域鏈声诸、this等
  • f 函數(shù)執(zhí)行完畢,f 函數(shù)上下文從執(zhí)行上下文棧中彈出
    了解到這個過程退盯,我們應該思考一個問題彼乌,那就是:
    當 f 函數(shù)執(zhí)行的時候,checkscope 函數(shù)上下文已經(jīng)被銷毀了啊(即從執(zhí)行上下文棧中被彈出)渊迁,怎么還會讀取到 checkscope 作用域下的 scope 值呢慰照?
    當我們了解了具體的執(zhí)行過程后,我們知道 f 執(zhí)行上下文維護了一個作用域鏈:
fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

對的琉朽,就是因為這個作用域鏈毒租,f 函數(shù)依然可以讀取到 checkscopeContext.AO 的值,說明當 f 函數(shù)引用了 checkscopeContext.AO 中的值的時候漓骚,即使 checkscopeContext 被銷毀了蝌衔,但是 JavaScript 依然會讓 checkscopeContext.AO 活在內(nèi)存中榛泛,f 函數(shù)依然可以通過 f 函數(shù)的作用域鏈找到它,正是因為 JavaScript 做到了這一點噩斟,從而實現(xiàn)了閉包這個概念曹锨。
所以,讓我們再看一遍實踐角度上閉包的定義:
1.即使創(chuàng)建它的上下文已經(jīng)銷毀剃允,它仍然存在(比如沛简,內(nèi)部函數(shù)從父函數(shù)中返回)
2.在代碼中引用了自由變量

必刷題

接下來,看這道刷題必刷斥废,面試必考的閉包題:

var data = [];
for(var i=0; i<3; i++) {
  data[i] = function() {
    console.log(i);
  }
}
data[0]();
data[1]();
data[2]();

答案是都是 3椒楣,讓我們分析一下原因:
當執(zhí)到data[0]函數(shù)之前,此時全局上下VO為:

globalContext = {
  vo:{
    data:[...],
    i:3
  }
}

當執(zhí)行 data[0] 函數(shù)的時候牡肉,data[0] 函數(shù)的作用域鏈為:

data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并沒有 i 值捧灰,所以會從 globalContext.VO 中查找,i 為 3统锤,所以打印的結果就是 3毛俏。
data[1] 和 data[2] 是一樣的道理。
所以讓我們改成閉包看看:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();

當執(zhí)行到 data[0] 函數(shù)之前饲窿,此時全局上下文的 VO 為:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

跟沒改之前一模一樣煌寇。

當執(zhí)行 data[0] 函數(shù)的時候,data[0] 函數(shù)的作用域鏈發(fā)生了改變:

data[0]Context = {
    Scope: [AO, 匿名函數(shù)Context.AO globalContext.VO]
}

( AO 表示活動對象逾雄,儲存了函數(shù)的參數(shù)阀溶、函數(shù)內(nèi)聲明的變量等)
匿名函數(shù)執(zhí)行上下文的AO為:

匿名函數(shù)Context = {
    AO: {
        arguments: {
            0: 0,
            length: 1
        },
        i: 0
    }
}

ata[0]Context 的 AO 并沒有 i 值,所以會沿著作用域鏈從匿名函數(shù) Context.AO 中查找鸦泳,這時候就會找 i 為 0银锻,找到了就不會往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值為3)辽故,所以打印的結果就是0徒仓。

data[1] 和 data[2] 是一樣的道理。

彩蛋

問題1:
為什么

globalContext = {
VO: {
data: [...],
i: 3
}
}

i為3 不是0誊垢,1掉弛,2
答:
當執(zhí)行到data[0]函數(shù)的時候,for循環(huán)已經(jīng)執(zhí)行完了喂走,i是全局變量殃饿,此時的值為3,舉個例子:

for (var i = 0; i < 3; i++) {}
console.log(i) // 3
for 循環(huán)結束后
data[0] = function(){console.log(i)}
data[1] = function(){console.log(i)}
data[2] = function(){console.log(i)}

執(zhí)行data[0] ()芋肠,data[1] ()乎芳,data[2] ()時,i=3,所以都打印3

繼續(xù)努力 加油。
原文詳見:
JavaScript深入之閉包

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奈惑,一起剝皮案震驚了整個濱河市吭净,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肴甸,老刑警劉巖寂殉,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異原在,居然都是意外死亡友扰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門庶柿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來村怪,“玉大人,你說我怎么就攤上這事浮庐∩醺海” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵审残,是天一觀的道長腊敲。 經(jīng)常有香客問我,道長维苔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任懂昂,我火速辦了婚禮介时,結果婚禮上,老公的妹妹穿的比我還像新娘凌彬。我一直安慰自己沸柔,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布铲敛。 她就那樣靜靜地躺著褐澎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伐蒋。 梳的紋絲不亂的頭發(fā)上工三,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音先鱼,去河邊找鬼俭正。 笑死,一個胖子當著我的面吹牛焙畔,可吹牛的內(nèi)容都是我干的掸读。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼儿惫!你這毒婦竟也來了澡罚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤肾请,失蹤者是張志新(化名)和其女友劉穎留搔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筐喳,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡催式,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了避归。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荣月。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梳毙,靈堂內(nèi)的尸體忽然破棺而出哺窄,到底是詐尸還是另有隱情,我是刑警寧澤账锹,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布萌业,位于F島的核電站,受9級特大地震影響奸柬,放射性物質(zhì)發(fā)生泄漏生年。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一廓奕、第九天 我趴在偏房一處隱蔽的房頂上張望抱婉。 院中可真熱鬧,春花似錦桌粉、人聲如沸蒸绩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽患亿。三九已至,卻和暖如春押逼,著一層夾襖步出監(jiān)牢的瞬間步藕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工宴胧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漱抓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓恕齐,卻偏偏與公主長得像乞娄,于是被迫代替她去往敵國和親瞬逊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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