理解js閉包

總結(jié):閉包的核心是[[scope]]屬性誓斥,在函數(shù)解析過(guò)程中只洒,如果函數(shù)引用了外層函數(shù)的變量,那么外層函數(shù)(即使自身被銷(xiāo)毀)的活動(dòng)對(duì)象帶著對(duì)應(yīng)變量將會(huì)被保留劳坑,并且記錄在scope屬性中毕谴,作為作用域鏈的第二層,如果還引用了外層函數(shù)的外層函數(shù)的變量距芬,那么對(duì)應(yīng)的活動(dòng)對(duì)象與變量也會(huì)被保留涝开,并記錄,將會(huì)作為作用域鏈的第三層蔑穴,依次類(lèi)推...忠寻。當(dāng)函數(shù)被調(diào)用時(shí),所取到的外部變量的值將會(huì)是調(diào)用時(shí)各個(gè)變量的值存和。即當(dāng)前值奕剃。

閉包調(diào)用時(shí)也是普通函數(shù)衷旅,只不過(guò)作用域鏈多了閉包Closure的成分

閉包的用途:1.模仿塊級(jí)作用域,在立即調(diào)用函數(shù)中聲明內(nèi)部需要使用的變量纵朋;2.管理私有變量和方法柿顶;3.函數(shù)柯里化

來(lái)個(gè)極端的例子

function closure() {
    let result = [], count = 1
    setInterval(() => {count++}, 1000)
    function outer() {
        let doufu = 'wu'
        return function inner() {
            let foo = 'foo'
            return function closureCallback() {
                let bar = {bar: 'bar'}, bar1 = 'bar'
                result[i] = function () {
                    console.log(count)
                    let b = doufu
                    return bar.bar + i
                }
            }
        }
    }
    let inner = outer()
    let closureCallback = inner()
    for (var i=0; i<10; i++) {
        // [[scope]]包含closure{result, i}
        // 函數(shù)在這里立即調(diào)用
        // i為實(shí)時(shí)值0,1,2,3...
        closureCallback()
    }
    return result
}
調(diào)用結(jié)果圖

從圖中可以看到,最終返回結(jié)果分別引用了closureCallback, outer操软,closure中的變量嘁锯,所以,在作用域鏈中會(huì)保存這三個(gè)函數(shù)的活動(dòng)對(duì)象聂薪,不同時(shí)間調(diào)用家乘,返回的count值不同,說(shuō)明引用的是當(dāng)前值藏澳。
以下是手工示意圖


scope構(gòu)成示意圖

以下是我的學(xué)習(xí)過(guò)程
JavaScript里面的閉包仁锯,指的是函數(shù)與聲明該函數(shù)的詞法環(huán)境的組合。
一般在函數(shù)里面聲明函數(shù)翔悠,并且引用外面函數(shù)的變量业崖,就會(huì)產(chǎn)生閉包。定義在全局的時(shí)候蓄愁,默認(rèn)不產(chǎn)生閉包(所謂閉包双炕,就是當(dāng)內(nèi)部函數(shù)引用了外部函數(shù)中的變量時(shí),會(huì)在函數(shù)的作用域上面添加一層撮抓,這一層包含了函數(shù)所引用的外部函數(shù)的變量妇斤,存放在scope屬性里面,在調(diào)用時(shí)丹拯,用于形成作用域鏈)
函數(shù)在執(zhí)行時(shí)趟济,會(huì)在內(nèi)存中創(chuàng)建一個(gè)活動(dòng)對(duì)象,該活動(dòng)對(duì)象包含arguments以及一些參數(shù)咽笼。并通過(guò)復(fù)制[[scope]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈。

var bar = 'zoo'
function Foo() {
    this.bar = bar
}
全局
function foo() {
    let bar = 'zoo'
    function zoo() {
        let zoo = bar
    }
}
foo()
函數(shù)內(nèi)
function closure() {
    let result = []
    for (var i=0; i<10; i++) {
        result[i] = function () {
            return i
        }
    }
    return result
}
let arr = closure()

主要體現(xiàn)在函數(shù)返回函數(shù)戚炫,函數(shù)A在調(diào)用函數(shù)B時(shí)被創(chuàng)建并從B函數(shù)的內(nèi)部返回剑刑。當(dāng)我們調(diào)用A函數(shù)的時(shí)候,B函數(shù)的作用域鏈已經(jīng)從內(nèi)存中銷(xiāo)毀双肤,但是我們?nèi)匀豢梢栽贏中訪問(wèn)B中存在的變量施掏。因?yàn)锽中的變量仍然保存在A的活動(dòng)對(duì)象中(作用域鏈中[[scope]]對(duì)象里面)
此時(shí),函數(shù)A與A的scope構(gòu)成closure函數(shù)的閉包實(shí)例

scope

從圖中可以看到茅糜,ar[0](以下稱(chēng)為函數(shù)A)函數(shù)的作用域鏈最頂層為自身活動(dòng)對(duì)象(arguments七芭, caller, length, name等等構(gòu)成)再往上則是由一個(gè)Closure對(duì)象實(shí)例構(gòu)成,可以看到這一層里面只包含一個(gè)變量i,即創(chuàng)建A時(shí)外層函數(shù)里面聲明的變量i蔑赘。當(dāng)我們調(diào)用A時(shí)狸驳,我們?cè)诘诙幼饔糜蜴溕厦嬲业降倪@個(gè)i變量预明。
為什么arr數(shù)組每一項(xiàng)都返回的是10,而不是對(duì)應(yīng)的下標(biāo)值的原因就在這里:當(dāng)我們調(diào)用數(shù)組項(xiàng)函數(shù)時(shí)耙箍,遇到變量i撰糠,并且在第二層作用域鏈讀取到i,這里面保存的i就是closure函數(shù)里面定義的i辩昆。在A調(diào)用時(shí)阅酪,closure已經(jīng)執(zhí)完畢,在closure執(zhí)行的過(guò)程中i的值從0變到了10汁针。這個(gè)性質(zhì)類(lèi)似于把原本存在于closure函數(shù)中的變量术辐,在closure函數(shù)執(zhí)行完畢后(從內(nèi)存中移除)我們可以在A自身的scope屬性里訪問(wèn)到。
簡(jiǎn)單一點(diǎn)說(shuō)就是我們?cè)谡{(diào)用A函數(shù)的時(shí)候施无,訪問(wèn)到的i變量辉词,是函數(shù)closure(雖然它不在了但是它的變量還在,仍然被scope屬性引用帆精。)的當(dāng)前值(實(shí)時(shí)值)

所以要達(dá)到預(yù)期目標(biāo)较屿,我們只需要保證它的scope對(duì)象中保存的這個(gè)‘i’值是正確的就可以了。
這里面的思路就是卓练,函數(shù)A被當(dāng)做普通函數(shù)調(diào)用時(shí)隘蝎,非閉包情況下,作用域就是自身(沒(méi)有i變量)+ 全局作用域(也沒(méi)有),所以這里還是需要借助閉包襟企。即需要保證A上一層作用域的i是正確的值

1.創(chuàng)建另外一個(gè)閉包,每個(gè)i的值都會(huì)創(chuàng)建一個(gè)閉包

function closureCallback(i) {
    // 返回函數(shù)里面的i變量就是closureCallback函數(shù)的參數(shù)i
    return function() {
        return i
    }
}

function closure() {
    let result = [], b = 'closure'
    for (var i=0; i<10; i++) {
                // 這里的closureCallback作為普通函數(shù)調(diào)用
                // 且沒(méi)有引用closure函數(shù)的變量嘱么,
                // 函數(shù)作用域內(nèi)的變量,無(wú)法直接在函數(shù)外取得
                // 所以作用域鏈不包含closure
                // 所以在closureCallback函數(shù)[[scope]]中不會(huì)有closure函數(shù)
        result[i] = closureCallback(i)
    }
    return result
}

2.使用匿名閉包

function closure() {
    let result = [], b = 'closure'
    for (var i=0; i<10; i++) {
        result[i] = (function (i) {
            return () => i
        })(i)
    }
    return result
}
let arr = closure()

3.使用let顽悼,減少閉包曼振,let具有塊級(jí)作用域的效果

function closure() {
    let result = [], b = 'closure'
    for (var i=0; i<10; i++) {
        let j = i
        result[i] = function () {
            return j
        }
    }
    return result
}
let arr = closure()
let形成塊級(jí)作用域
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蔚龙,隨后出現(xiàn)的幾起案子冰评,更是在濱河造成了極大的恐慌,老刑警劉巖木羹,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甲雅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坑填,警方通過(guò)查閱死者的電腦和手機(jī)抛人,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)脐瑰,“玉大人妖枚,你說(shuō)我怎么就攤上這事〔栽冢” “怎么了绝页?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵荠商,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我抒寂,道長(zhǎng)结啼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任屈芜,我火速辦了婚禮郊愧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘井佑。我一直安慰自己属铁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布躬翁。 她就那樣靜靜地躺著焦蘑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盒发。 梳的紋絲不亂的頭發(fā)上例嘱,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音宁舰,去河邊找鬼拼卵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蛮艰,可吹牛的內(nèi)容都是我干的腋腮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼壤蚜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼即寡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起袜刷,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤聪富,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后著蟹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體善涨,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年草则,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟹漓。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炕横,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葡粒,到底是詐尸還是另有隱情份殿,我是刑警寧澤膜钓,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站卿嘲,受9級(jí)特大地震影響颂斜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拾枣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一沃疮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梅肤,春花似錦司蔬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至左医,卻和暖如春授帕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浮梢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工跛十, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人黔寇。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓偶器,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缝裤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屏轰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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

  • 函數(shù)只能在其所在的作用域內(nèi)調(diào)用嗎?怎樣在一個(gè)函數(shù)所在的作用域之外調(diào)用該函數(shù)憋飞?比如下面代碼霎苗,函數(shù)bar定義在函數(shù)fo...
    閆浩奇閱讀 497評(píng)論 0 4
  • 歡迎移步我的博客閱讀:《理解閉包》 閉包 是指可以包含自由(未綁定到特定對(duì)象)變量的代碼塊;這些變量不是在這個(gè)代碼...
    Jovey閱讀 495評(píng)論 0 3
  • 原文地址:深入理解閉包(六)——閉包 終于講到閉包了榛做,這一路走來(lái)不容易唁盏。從前面的博文中我們知道,js的垃圾回收機(jī)制...
    薛普定朗諤克閱讀 1,368評(píng)論 0 3
  • 001 認(rèn)知錯(cuò)覺(jué) 要認(rèn)識(shí)到認(rèn)知錯(cuò)覺(jué)的客觀存在,但是并不需要時(shí)時(shí)保持警覺(jué)锰瘸,只需要在做重大決定時(shí)甄別并排除這種成見(jiàn)對(duì)自...
    李知白閱讀 213評(píng)論 0 3
  • 夜十點(diǎn) 雨滴嘩嘩落下刽严, 路上行人卻許多, 有下班的避凝,有逛街的舞萄, 有打傘的眨补,也有不打傘的, 總之步履匆匆倒脓, 也有欣賞...
    四葉草_75c4閱讀 153評(píng)論 0 3