• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當(dāng)前位置: 首頁(yè) - 科技 - 知識(shí)百科 - 正文

    JavaScript閉包的底層運(yùn)行機(jī)制

    來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 20:31:50
    文檔

    JavaScript閉包的底層運(yùn)行機(jī)制

    JavaScript閉包的底層運(yùn)行機(jī)制:Wikipedia給出的解釋并沒有太大的幫助。閉包是什么時(shí)候被創(chuàng)建的,什么時(shí)候被銷毀的?具體的實(shí)現(xiàn)又是怎么樣的?use strict; var myClosure = (function outerFunction() { var hidden = 1; return { inc: function inn
    推薦度:
    導(dǎo)讀JavaScript閉包的底層運(yùn)行機(jī)制:Wikipedia給出的解釋并沒有太大的幫助。閉包是什么時(shí)候被創(chuàng)建的,什么時(shí)候被銷毀的?具體的實(shí)現(xiàn)又是怎么樣的?use strict; var myClosure = (function outerFunction() { var hidden = 1; return { inc: function inn
    Wikipedia給出的解釋并沒有太大的幫助。閉包是什么時(shí)候被創(chuàng)建的,什么時(shí)候被銷毀的?具體的實(shí)現(xiàn)又是怎么樣的?

    "use strict";
    var myClosure = (function outerFunction() {
     var hidden = 1;
     return {
     inc: function innerFunction() {
     return hidden++;
     }
     };
    }());
    myClosure.inc(); // 返回 1
    myClosure.inc(); // 返回 2
    myClosure.inc(); // 返回 3
    // 相信對(duì)JS熟悉的朋友都能很快理解這段代碼
    // 那么在這段代碼運(yùn)行的背后究竟發(fā)生了怎樣的事情呢?

    現(xiàn)在,我終于知道了答案,我感到很興奮并且決定向大家解釋這個(gè)答案。至少,我一定是不會(huì)忘記這個(gè)答案的。

    1.png

    并且,在我閱讀與閉包相關(guān)的現(xiàn)存的資料時(shí),我很努力地嘗試著去在腦海中想想每個(gè)事物之間的聯(lián)系:對(duì)象之間是如何引用的,對(duì)象之間的繼承關(guān)系是什么,等等。我找不到關(guān)于這些負(fù)責(zé)關(guān)系的很好的圖表,于是我決定自己畫一些。

    我將假設(shè)讀者對(duì)JavaScript已經(jīng)比較熟悉了,知道什么是全局對(duì)象,知道函數(shù)在JavaScript當(dāng)中是“first-class objects”,等等。

    作用域鏈(Scope Chain)

    當(dāng)JavaScript在運(yùn)行的時(shí)候,它需要一些空間讓它來存儲(chǔ)本地變量(local variables)。我們將這些空間稱為作用域?qū)ο螅⊿cope object),有時(shí)候也稱作LexicalEnvironment。例如,當(dāng)你調(diào)用函數(shù)時(shí),函數(shù)定義了一些本地變量,這些變量就被存儲(chǔ)在一個(gè)作用域?qū)ο笾?。你可以將作用域函?shù)想象成一個(gè)普通的JavaScript對(duì)象,但是有一個(gè)很大的區(qū)別就是你不能夠直接在JavaScript當(dāng)中直接獲取這個(gè)對(duì)象。你只可以修改這個(gè)對(duì)象的屬性,但是你不能夠獲取這個(gè)對(duì)象的引用。

    作用域?qū)ο蟮母拍钍沟肑avaScript和C、C++非常不同。在C、C++中,本地變量被保存在棧(stack)中。在JavaScript中,作用域?qū)ο笫窃诙阎斜粍?chuàng)建的(至少表現(xiàn)出來的行為是這樣的),所以在函數(shù)返回后它們也還是能夠被訪問到而不被銷毀。

    正如你做想的,作用域?qū)ο笫强梢杂懈缸饔糜驅(qū)ο螅╬arent scope object)的。當(dāng)代碼試圖訪問一個(gè)變量的時(shí)候,解釋器將在當(dāng)前的作用域?qū)ο笾胁檎疫@個(gè)屬性。如果這個(gè)屬性不存在,那么解釋器就會(huì)在父作用域?qū)ο笾胁檎疫@個(gè)屬性。就這樣,一直向父作用域?qū)ο蟛檎?,直到找到該屬性或者再也沒有父作用域?qū)ο?。我們將這個(gè)查找變量的過程中所經(jīng)過的作用域?qū)ο蟪俗饔糜蜴湥⊿cope chain)。

    在作用域鏈中查找變量的過程和原型繼承(prototypal inheritance)有著非常相似之處。但是,非常不一樣的地方在于,當(dāng)你在原型鏈(prototype chain)中找不到一個(gè)屬性的時(shí)候,并不會(huì)引發(fā)一個(gè)錯(cuò)誤,而是會(huì)得到undefined。但是如果你試圖訪問一個(gè)作用域鏈中不存在的屬性的話,你就會(huì)得到一個(gè)ReferenceError。

    在作用域鏈的最頂層的元素就是全局對(duì)象(Global Object)了。運(yùn)行在全局環(huán)境的JavaScript代碼中,作用域鏈?zhǔn)冀K只含有一個(gè)元素,那就是全局對(duì)象。所以,當(dāng)你在全局環(huán)境中定義變量的時(shí)候,它們就會(huì)被定義到全局對(duì)象中。當(dāng)函數(shù)被調(diào)用的時(shí)候,作用域鏈就會(huì)包含多個(gè)作用域?qū)ο蟆?/p>

    全局環(huán)境中運(yùn)行的代碼

    好了,理論就說到這里。接下來我們來從實(shí)際的代碼入手。

    // my_script.js
    "use strict";
    var foo = 1;
    var bar = 2;

    我們?cè)谌汁h(huán)境中創(chuàng)建了兩個(gè)變量。正如我剛才所說,此時(shí)的作用域?qū)ο缶褪侨謱?duì)象。

    1.png

    在上面的代碼中,我們有一個(gè)執(zhí)行的上下文(myscript.js自身的代碼),以及它所引用的作用域?qū)ο?。全局?duì)象里面還含有很多不同的屬性,在這里我們就忽略掉了。

    沒有被嵌套的函數(shù)(Non-nested functions)

    接下來,我們看這段代碼

    "use strict";
    var foo = 1;
    var bar = 2;
    function myFunc() {
     //-- define local-to-function variables
     var a = 1;
     var b = 2;
     var foo = 3;
     console.log("inside myFunc");
    }
    console.log("outside");
    //-- and then, call it:
    myFunc();

    當(dāng)myFunc被定義的時(shí)候,myFunc的標(biāo)識(shí)符(identifier)就被加到了當(dāng)前的作用域?qū)ο笾校ㄔ谶@里就是全局對(duì)象),并且這個(gè)標(biāo)識(shí)符所引用的是一個(gè)函數(shù)對(duì)象(function object)。函數(shù)對(duì)象中所包含的是函數(shù)的源代碼以及其他的屬性。其中一個(gè)我們所關(guān)心的屬性就是內(nèi)部屬性[[scope]]。[[scope]]所指向的就是當(dāng)前的作用域?qū)ο?。也就是指的就是函?shù)的標(biāo)識(shí)符被創(chuàng)建的時(shí)候,我們所能夠直接訪問的那個(gè)作用域?qū)ο螅ㄔ谶@里就是全局對(duì)象)。

    1.png

    所以,在console.log("outside")被運(yùn)行之前,對(duì)象之間的關(guān)系是如下圖所示。

    1.png

    溫習(xí)一下。myFunc所引用的函數(shù)對(duì)象其本身不僅僅含有函數(shù)的代碼,并且還含有指向其被創(chuàng)建的時(shí)候的作用域?qū)ο蟆_@一點(diǎn)非常重要!

    當(dāng)myFunc函數(shù)被調(diào)用的時(shí)候,一個(gè)新的作用域?qū)ο蟊粍?chuàng)建了。新的作用域?qū)ο笾邪琺yFunc函數(shù)所定義的本地變量,以及其參數(shù)(arguments)。這個(gè)新的作用域?qū)ο蟮母缸饔糜驅(qū)ο缶褪窃谶\(yùn)行myFunc時(shí)我們所能直接訪問的那個(gè)作用域?qū)ο蟆?/p>

    所以,當(dāng)myFunc被執(zhí)行的時(shí)候,對(duì)象之間的關(guān)系如下圖所示。

    1.png

    現(xiàn)在我們就擁有了一個(gè)作用域鏈。當(dāng)我們?cè)噲D在myFunc當(dāng)中訪問某些變量的時(shí)候,JavaScript會(huì)先在其能直接訪問的作用域?qū)ο螅ㄟ@里就是myFunc() scope)當(dāng)中查找這個(gè)屬性。如果找不到,那么就在它的父作用域?qū)ο螽?dāng)中查找(在這里就是Global Object)。如果一直往上找,找到?jīng)]有父作用域?qū)ο鬄橹惯€沒有找到的話,那么就會(huì)拋出一個(gè)ReferenceError。

    例如,如果我們?cè)趍yFunc中要訪問a這個(gè)變量,那么在myFunc scope當(dāng)中就可以找到它,得到值為1。

    如果我們嘗試訪問foo,我們就會(huì)在myFunc() scope中得到3。只有在myFunc() scope里面找不到foo的時(shí)候,JavaScript才會(huì)往Global Object去查找。所以,這里我們不會(huì)訪問到Global Object里面的foo。

    如果我們嘗試訪問bar,我們?cè)趍yFunc() scope當(dāng)中找不到它,于是就會(huì)在Global Object當(dāng)中查找,因此查找到2。

    很重要的是,只要這些作用域?qū)ο笠廊槐灰?,它們就不?huì)被垃圾回收器(garbage collector)銷毀,我們就一直能訪問它們。當(dāng)然,當(dāng)引用一個(gè)作用域?qū)ο蟮淖詈笠粋€(gè)引用被解除的時(shí)候,并不代表垃圾回收器會(huì)立刻回收它,只是它現(xiàn)在可以被回收了。

    所以,當(dāng)myFunc()返回的時(shí)候,再也沒有人引用myFunc() scope了。當(dāng)垃圾回收結(jié)束后,對(duì)象之間的關(guān)系變成回了調(diào)用前的關(guān)系。

    1.png

    接下來,為了圖表直觀起見,我將不再將函數(shù)對(duì)象畫出來。但是,請(qǐng)永遠(yuǎn)記著,函數(shù)對(duì)象里面的[[scope]]屬性,保存著該函數(shù)被定義的時(shí)候所能夠直接訪問的作用域?qū)ο蟆?/p>

    嵌套的函數(shù)(Nested functions)

    正如前面所說,當(dāng)一個(gè)函數(shù)返回后,沒有其他對(duì)象會(huì)保存對(duì)其的引用。所以,它就可能被垃圾回收器回收。但是如果我們?cè)诤瘮?shù)當(dāng)中定義嵌套的函數(shù)并且返回,被調(diào)用函數(shù)的一方所存儲(chǔ)呢?(如下面的代碼)

    function myFunc() {
     return innerFunc() {
     // ...
     }
    }
    var innerFunc = myFunc();

    你已經(jīng)知道的是,函數(shù)對(duì)象中總是有一個(gè)[[scope]]屬性,保存著該函數(shù)被定義的時(shí)候所能夠直接訪問的作用域?qū)ο?。所以,?dāng)我們?cè)诙x嵌套的函數(shù)的時(shí)候,這個(gè)嵌套的函數(shù)的[[scope]]就會(huì)引用外圍函數(shù)(Outer function)的當(dāng)前作用域?qū)ο蟆?/p>

    如果我們將這個(gè)嵌套函數(shù)返回,并被另外一個(gè)地方的標(biāo)識(shí)符所引用的話,那么這個(gè)嵌套函數(shù)及其[[scope]]所引用的作用域?qū)ο缶筒粫?huì)被垃圾回收所銷毀。

    "use strict";
    function createCounter(initial) {
     var counter = initial;
     function increment(value) {
     counter += value;
     }
     function get() {
     return counter;
     }
     return {
     increment: increment,
     get: get
     };
    }
    var myCounter = createCounter(100);
    console.log(myCounter.get()); // 返回 100
    myCounter.increment(5);
    console.log(myCounter.get()); // 返回 105

    當(dāng)我們調(diào)用createCounter(100)的那一瞬間,對(duì)象之間的關(guān)系如下圖

    1.png

    注意increment和get函數(shù)都存有指向createCounter(100) scope的引用。如果createCounter(100)沒有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因?yàn)閏reateCounter(100)實(shí)際上是有返回值的,并且返回值被存儲(chǔ)在了myCounter中,所以對(duì)象之間的引用關(guān)系變成了如下圖所示

    1.png

    所以,createCounter(100)雖然已經(jīng)返回了,但是它的作用域?qū)ο笠廊淮嬖?,可以且僅只能被嵌套的函數(shù)(increment和get)所訪問。

    讓我們?cè)囍\(yùn)行myCounter.get()。剛才說過,函數(shù)被調(diào)用的時(shí)候會(huì)創(chuàng)建一個(gè)新的作用域?qū)ο螅⑶以撟饔糜驅(qū)ο蟮母缸饔糜驅(qū)ο髸?huì)是當(dāng)前可以直接訪問的作用域?qū)ο?。所以,?dāng)myCounter.get()被調(diào)用時(shí)的一瞬間,對(duì)象之間的關(guān)系如下。

    1.png

    在myCounter.get()運(yùn)行的過程中,作用域鏈最底層的對(duì)象就是get() scope,這是一個(gè)空對(duì)象。所以,當(dāng)myCounter.get()訪問counter變量時(shí),JavaScript在get() scope中找不到這個(gè)屬性,于是就向上到createCounter(100) scope當(dāng)中查找。然后,myCounter.get()將這個(gè)值返回。

    調(diào)用myCounter.increment(5)的時(shí)候,事情變得更有趣了,因?yàn)檫@個(gè)時(shí)候函數(shù)調(diào)用的時(shí)候傳入了參數(shù)。

    1.png

    正如你所見,increment(5)的調(diào)用創(chuàng)建了一個(gè)新的作用域?qū)ο?,并且其中含有傳入的參?shù)value。當(dāng)這個(gè)函數(shù)嘗試訪問value的時(shí)候,JavaScript立刻就能在當(dāng)前的作用域?qū)ο笳业剿?。然而,這個(gè)函數(shù)試圖訪問counter的時(shí)候,JavaScript無法在當(dāng)前的作用域?qū)ο笳业剿谑蔷蜁?huì)在其父作用域createCounter(100) scope中查找。

    我們可以注意到,在createCounter函數(shù)之外,除了被返回的get和increment兩個(gè)方法,沒有其他的地方可以訪問到value這個(gè)變量了。這就是用閉包實(shí)現(xiàn)“私有變量”的方法。

    我們注意到initial變量也被存儲(chǔ)在createCounter()所創(chuàng)建的作用域?qū)ο笾?,盡管它沒有被用到。所以,我們實(shí)際上可以去掉var counter = initial;,將initial改名為counter。但是為了代碼的可讀性起見,我們保留原有的代碼不做變化。

    需要注意的是作用域鏈?zhǔn)遣粫?huì)被復(fù)制的。每次函數(shù)調(diào)用只會(huì)往作用域鏈下面新增一個(gè)作用域?qū)ο?。所以,如果在函?shù)調(diào)用的過程當(dāng)中對(duì)作用域鏈中的任何一個(gè)作用域?qū)ο蟮淖兞窟M(jìn)行修改的話,那么同時(shí)作用域鏈中也擁有該作用域?qū)ο蟮暮瘮?shù)對(duì)象也是能夠訪問到這個(gè)變化后的變量的。

    這也就是為什么下面這個(gè)大家都很熟悉的例子會(huì)不能產(chǎn)出我們想要的結(jié)果。

    "use strict";
    var elems = document.getElementsByClassName("myClass"), i;
    for (i = 0; i < elems.length; i++) {
     elems[i].addEventListener("click", function () {
     this.innerHTML = i;
     });
    }

    在上面的循環(huán)中創(chuàng)建了多個(gè)函數(shù)對(duì)象,所有的函數(shù)對(duì)象的[[scope]]都保存著對(duì)當(dāng)前作用域?qū)ο蟮囊?。而變量i正好就在當(dāng)前作用域鏈中,所以循環(huán)每次對(duì)i的修改,對(duì)于每個(gè)函數(shù)對(duì)象都是能夠看到的。

    “看起來一樣的”函數(shù),不一樣的作用域?qū)ο?/p>

    現(xiàn)在我們來看一個(gè)更有趣的例子。

    "use strict";
    function createCounter(initial) {
     // ...
    }
    var myCounter1 = createCounter(100);
    var myCounter2 = createCounter(200);

    當(dāng)myCounter1和myCounter2被創(chuàng)建后,對(duì)象之間的關(guān)系為

    1.png

    在上面的例子中,myCounter1.increment和myCounter2.increment的函數(shù)對(duì)象擁有著一樣的代碼以及一樣的屬性值(name,length等等),但是它們的[[scope]]指向的是不一樣的作用域?qū)ο蟆?/p>

    這才有了下面的結(jié)果

    var a, b;
    a = myCounter1.get(); // a 等于 100
    b = myCounter2.get(); // b 等于 200
    myCounter1.increment(1);
    myCounter1.increment(2);
    myCounter2.increment(5);
    a = myCounter1.get(); // a 等于 103
    b = myCounter2.get(); // b 等于 205

    作用域鏈和this

    this的值不會(huì)被保存在作用域鏈中,this的值取決于函數(shù)被調(diào)用的時(shí)候的情景。

    1.png

    總結(jié)

    讓我們來回想我們?cè)诒疚拈_頭提到的一些問題。

    什么是閉包?閉包就是同時(shí)含有對(duì)函數(shù)對(duì)象以及作用域?qū)ο笠玫淖钕?。?shí)際上,所有JavaScript對(duì)象都是閉包。

    閉包是什么時(shí)候被創(chuàng)建的?因?yàn)樗蠮avaScript對(duì)象都是閉包,因此,當(dāng)你定義一個(gè)函數(shù)的時(shí)候,你就定義了一個(gè)閉包。

    閉包是什么時(shí)候被銷毀的?當(dāng)它不被任何其他的對(duì)象引用的時(shí)候。

    專有名詞翻譯表

    本文采用下面的專有名詞翻譯表,如有更好的翻譯請(qǐng)告知,尤其是加*的翻譯

    *全局環(huán)境中運(yùn)行的代碼:top-level code

    參數(shù):arguments

    作用域?qū)ο螅篠cope object

    作用域鏈:Scope Chain

    棧:stack

    原型繼承:prototypal inheritance

    原型鏈:prototype chain

    全局對(duì)象:Global Object

    標(biāo)識(shí)符:identifier

    垃圾回收器:garbage collector

    著作權(quán)聲明

    本文經(jīng)授權(quán)翻譯自How do JavaScript closures work under the hood。

    譯者對(duì)原文進(jìn)行了描述上的一些修改。但在沒有特殊注明的情況下,譯者表述的意思和原文保持一致。

    聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    JavaScript閉包的底層運(yùn)行機(jī)制

    JavaScript閉包的底層運(yùn)行機(jī)制:Wikipedia給出的解釋并沒有太大的幫助。閉包是什么時(shí)候被創(chuàng)建的,什么時(shí)候被銷毀的?具體的實(shí)現(xiàn)又是怎么樣的?use strict; var myClosure = (function outerFunction() { var hidden = 1; return { inc: function inn
    推薦度:
    標(biāo)簽: js 運(yùn)作 javascript
    • 熱門焦點(diǎn)

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 精品熟女少妇a∨免费久久| 国产日韩精品欧美一区| 亚洲国产精品无码专区影院 | 国产精品久久久久国产A级| 日本精品夜色视频一区二区| 国产亚洲婷婷香蕉久久精品| 狼色精品人妻在线视频| 无码精品第一页| 免费精品久久久久久中文字幕| 99久久精品免费看国产| 久久久国产精品网站| 99re久久精品国产首页2020| 精品久久久久久无码中文字幕一区| 一区二区三区精品高清视频免费在线播放| 精品久久久久久无码免费| 国产精品国产三级国产| 91精品无码久久久久久五月天| 国产成人精品日本亚洲直接 | 精品国产VA久久久久久久冰| 尤物TV国产精品看片在线| 日本精品一区二区三区在线视频一| 国产精品福利电影一区二区三区四区欧美白嫩精品 | 久久er国产精品免费观看2| 99久久免费国产精精品| 99久久久国产精品免费无卡顿 | 日韩精品无码免费一区二区三区 | 久久精品国产亚洲77777| 日韩精品人妻系列无码专区| 亚洲韩国精品无码一区二区三区 | 精品国产一区二区三区久久久狼 | 亚洲国产精品嫩草影院| 久久国产精品99久久久久久老狼| 精品午夜久久福利大片| 久久久九九有精品国产| 免费欧美精品a在线| 午夜精品福利视频| 国产精品V亚洲精品V日韩精品 | 亚洲精品乱码久久久久久久久久久久| 一本久久a久久精品综合香蕉| 日韩精品系列产品| 国产日韩精品欧美一区喷水|