亚洲av午夜福利精品一区人妖,亚洲乱码日产精品a级毛片久久,91精品视频观看,青草青草久热精品视频在线观看

JavaScript 閉包的底層運行機制

2016-9-28    藍藍設計的小編

如果您想訂閱本博客內容,每天自動發到您的郵箱中, 請點這里

我研究JavaScript 閉包(closure)已經有一段時間了。我之前只是學會了如何使用它們,而沒有透徹地了解它們具體是如何運作的。那么,究竟什么是閉包?

Wikipedia給出的解釋并沒有太大的幫助。閉包是什么時候被創建的,什么時候被銷毀的?具體的實現又是怎么樣的?

"use strict"; var myClosure = (function outerFunction() { var hidden = 1; return {
    inc: function innerFunction() { return hidden++;
    }
  };

}());

myClosure.inc(); // 返回 1 myClosure.inc(); // 返回 2 myClosure.inc(); // 返回 3 // 相信對JS熟悉的朋友都能很快理解這段代碼 // 那么在這段代碼運行的背后究竟發生了怎樣的事情呢?

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

Tell me and I forget. Teach me and I remember. Involve me and I learn.
© Benjamin Franklin

并且,在我閱讀與閉包相關的現存的資料時,我很努力地嘗試著去在腦海中想想每個事物之間的聯系:對象之間是如何引用的,對象之間的繼承關系是什么,等等。我找不到關于這些負責關系的很好的圖表,于是我決定自己畫一些。

我將假設讀者對JavaScript已經比較熟悉了,知道什么是全局對象,知道函數在JavaScript當中是“first-class objects”,等等。

作用域鏈(Scope Chain)

當JavaScript在運行的時候,它需要一些空間讓它來存儲本地變量(local variables)。我們將這些空間稱為作用域對象(Scope object),有時候也稱作LexicalEnvironment。例如,當你調用函數時,函數定義了一些本地變量,這些變量就被存儲在一個作用域對象中。你可以將作用域函數想象成一個普通的JavaScript對象,但是有一個很大的區別就是你不能夠直接在JavaScript當中直接獲取這個對象。你只可以修改這個對象的屬性,但是你不能夠獲取這個對象的引用。

作用域對象的概念使得JavaScript和C、C++非常不同。在C、C++中,本地變量被保存在棧(stack)中。在JavaScript中,作用域對象是在堆中被創建的(至少表現出來的行為是這樣的),所以在函數返回后它們也還是能夠被訪問到而不被銷毀。

正如你做想的,作用域對象是可以有父作用域對象(parent scope object)的。當代碼試圖訪問一個變量的時候,解釋器將在當前的作用域對象中查找這個屬性。如果這個屬性不存在,那么解釋器就會在父作用域對象中查找這個屬性。就這樣,一直向父作用域對象查找,直到找到該屬性或者再也沒有父作用域對象。我們將這個查找變量的過程中所經過的作用域對象乘坐作用域鏈(Scope chain)。

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

在作用域鏈的最頂層的元素就是全局對象(Global Object)了。運行在全局環境的JavaScript代碼中,作用域鏈始終只含有一個元素,那就是全局對象。所以,當你在全局環境中定義變量的時候,它們就會被定義到全局對象中。當函數被調用的時候,作用域鏈就會包含多個作用域對象。

全局環境中運行的代碼

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

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

我們在全局環境中創建了兩個變量。正如我剛才所說,此時的作用域對象就是全局對象。

1.png

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

沒有被嵌套的函數(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();

myFunc被定義的時候,myFunc的標識符(identifier)就被加到了當前的作用域對象中(在這里就是全局對象),并且這個標識符所引用的是一個函數對象(function object)。函數對象中所包含的是函數的源代碼以及其他的屬性。其中一個我們所關心的屬性就是內部屬性[[scope]][[scope]]所指向的就是當前的作用域對象。也就是指的就是函數的標識符被創建的時候,我們所能夠直接訪問的那個作用域對象(在這里就是全局對象)。

“直接訪問”的意思就是,在當前作用域鏈中,該作用域對象處于最底層,沒有子作用域對象。

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

2.png

溫習一下。myFunc所引用的函數對象其本身不僅僅含有函數的代碼,并且還含有指向其被創建的時候的作用域對象。這一點非常重要!

myFunc函數被調用的時候,一個新的作用域對象被創建了。新的作用域對象中包含myFunc函數所定義的本地變量,以及其參數(arguments)。這個新的作用域對象的父作用域對象就是在運行myFunc時我們所能直接訪問的那個作用域對象。

所以,當myFunc被執行的時候,對象之間的關系如下圖所示。

3.png

現在我們就擁有了一個作用域鏈。當我們試圖在myFunc當中訪問某些變量的時候,JavaScript會先在其能直接訪問的作用域對象(這里就是myFunc() scope)當中查找這個屬性。如果找不到,那么就在它的父作用域對象當中查找(在這里就是Global Object)。如果一直往上找,找到沒有父作用域對象為止還沒有找到的話,那么就會拋出一個ReferenceError

例如,如果我們在myFunc中要訪問a這個變量,那么在myFunc scope當中就可以找到它,得到值為1。

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

如果我們嘗試訪問bar,我們在myFunc() scope當中找不到它,于是就會在Global Object當中查找,因此查找到2。

很重要的是,只要這些作用域對象依然被引用,它們就不會被垃圾回收器(garbage collector)銷毀,我們就一直能訪問它們。當然,當引用一個作用域對象的最后一個引用被解除的時候,并不代表垃圾回收器會立刻回收它,只是它現在可以被回收了。

所以,當myFunc()返回的時候,再也沒有人引用myFunc() scope了。當垃圾回收結束后,對象之間的關系變成回了調用前的關系。

4.png

接下來,為了圖表直觀起見,我將不再將函數對象畫出來。但是,請永遠記著,函數對象里面的[[scope]]屬性,保存著該函數被定義的時候所能夠直接訪問的作用域對象。

嵌套的函數(Nested functions)

正如前面所說,當一個函數返回后,沒有其他對象會保存對其的引用。所以,它就可能被垃圾回收器回收。但是如果我們在函數當中定義嵌套的函數并且返回,被調用函數的一方所存儲呢?(如下面的代碼)

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

你已經知道的是,函數對象中總是有一個[[scope]]屬性,保存著該函數被定義的時候所能夠直接訪問的作用域對象。所以,當我們在定義嵌套的函數的時候,這個嵌套的函數的[[scope]]就會引用外圍函數(Outer function)的當前作用域對象。

如果我們將這個嵌套函數返回,并被另外一個地方的標識符所引用的話,那么這個嵌套函數及其[[scope]]所引用的作用域對象就不會被垃圾回收所銷毀。

"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

當我們調用createCounter(100)的那一瞬間,對象之間的關系如下圖

5.png

注意incrementget函數都存有指向createCounter(100) scope的引用。如果createCounter(100)沒有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因為createCounter(100)實際上是有返回值的,并且返回值被存儲在了myCounter中,所以對象之間的引用關系變成了如下圖所示

6.png

所以,createCounter(100)雖然已經返回了,但是它的作用域對象依然存在,可以且僅只能被嵌套的函數(incrementget)所訪問。

讓我們試著運行myCounter.get()。剛才說過,函數被調用的時候會創建一個新的作用域對象,并且該作用域對象的父作用域對象會是當前可以直接訪問的作用域對象。所以,當myCounter.get()被調用時的一瞬間,對象之間的關系如下。

7.png

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

調用myCounter.increment(5)的時候,事情變得更有趣了,因為這個時候函數調用的時候傳入了參數。

8.png

正如你所見,increment(5)的調用創建了一個新的作用域對象,并且其中含有傳入的參數value。當這個函數嘗試訪問value的時候,JavaScript立刻就能在當前的作用域對象找到它。然而,這個函數試圖訪問counter的時候,JavaScript無法在當前的作用域對象找到它,于是就會在其父作用域createCounter(100) scope中查找。

我們可以注意到,在createCounter函數之外,除了被返回的getincrement兩個方法,沒有其他的地方可以訪問到value這個變量了。這就是用閉包實現“私有變量”的方法。

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

需要注意的是作用域鏈是不會被復制的。每次函數調用只會往作用域鏈下面新增一個作用域對象。所以,如果在函數調用的過程當中對作用域鏈中的任何一個作用域對象的變量進行修改的話,那么同時作用域鏈中也擁有該作用域對象的函數對象也是能夠訪問到這個變化后的變量的。

這也就是為什么下面這個大家都很熟悉的例子會不能產出我們想要的結果。

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

在上面的循環中創建了多個函數對象,所有的函數對象的[[scope]]都保存著對當前作用域對象的引用。而變量i正好就在當前作用域鏈中,所以循環每次對i的修改,對于每個函數對象都是能夠看到的。

“看起來一樣的”函數,不一樣的作用域對象

現在我們來看一個更有趣的例子。

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

myCounter1myCounter2被創建后,對象之間的關系為

9.png

在上面的例子中,myCounter1.incrementmyCounter2.increment的函數對象擁有著一樣的代碼以及一樣的屬性值(name,length等等),但是它們的[[scope]]指向的是不一樣的作用域對象

這才有了下面的結果

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的值不會被保存在作用域鏈中,this的值取決于函數被調用的時候的情景。

譯者注:對這部分,譯者自己曾經寫過一篇更加詳盡的文章,請參考《用自然語言的角度理解JavaScript中的this關鍵字》。原文的這一部分以及“this在嵌套的函數中的使用”譯者便不再翻譯。

總結

讓我們來回想我們在本文開頭提到的一些問題。

  • 什么是閉包?閉包就是同時含有對函數對象以及作用域對象引用的最想。實際上,所有JavaScript對象都是閉包。
  • 閉包是什么時候被創建的?因為所有JavaScript對象都是閉包,因此,當你定義一個函數的時候,你就定義了一個閉包。
  • 閉包是什么時候被銷毀的?當它不被任何其他的對象引用的時候。

專有名詞翻譯表

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

  • *全局環境中運行的代碼:top-level code
  • 參數:arguments
  • 作用域對象:Scope object
  • 作用域鏈:Scope Chain
  • 棧:stack
  • 原型繼承:prototypal inheritance
  • 原型鏈:prototype chain
  • 全局對象:Global Object
  • 標識符:identifier
  • 垃圾回收器:garbage collector

 

 

藍藍設計m.skdbbs.com )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務 

 

日歷

鏈接

個人資料

藍藍設計的小編 http://m.skdbbs.com

存檔

亚洲av午夜福利精品一区人妖,亚洲乱码日产精品a级毛片久久,91精品视频观看,青草青草久热精品视频在线观看
<strike id="cy2gs"><menu id="cy2gs"></menu></strike>
  • <del id="cy2gs"><dfn id="cy2gs"></dfn></del>
  • 久久人人精品| 你懂的网址国产 欧美| 国内伊人久久久久久网站视频| 欧美自拍丝袜亚洲| 亚洲永久免费视频| 99亚洲一区二区| 99综合在线| 亚洲精品色图| 国产一区二区精品丝袜| 国产精品久久久久免费a∨大胸| 免费不卡欧美自拍视频| 欧美一区二区成人6969| 亚洲午夜av在线| 亚洲激情综合| 亚洲国产激情| 亚洲国产精品免费| 久久精品在线免费观看| 午夜一区不卡| 欧美日韩一区二区三区高清| 久久精品国产亚洲高清剧情介绍| 久久人人爽人人| 国产精品成人国产乱一区| 一区二区三区在线观看国产| 亚洲一区精品在线| 国产一区二区精品久久| 国产精品久久网站| 欧美日韩成人精品| 免费成人黄色| 久久亚洲精品伦理| 久久免费高清视频| 欧美成人精品不卡视频在线观看| 久久久亚洲人| 久久精品成人一区二区三区蜜臀| 久久婷婷国产麻豆91天堂| 久久蜜桃香蕉精品一区二区三区| 亚洲综合色婷婷| 亚洲伊人久久综合| 性欧美超级视频| 欧美一区二区视频97| 国产精品99久久久久久有的能看| 国产亚洲欧美在线| 国产精品美女在线观看| 国产精品日韩| 国产一区二区三区视频在线观看| 国产精品亚洲一区二区三区在线| 国产精品丝袜91| 国产一区二区你懂的| 在线观看亚洲精品视频| 亚洲精品日韩精品| 亚洲视频1区| 欧美一区二区三区啪啪| 久久久国产一区二区三区| 国产日韩精品一区二区三区在线| 久久中文字幕一区二区三区| 久久精品一二三区| 国产一区二区三区日韩| 国产精品亚发布| 国产精品成人免费| 国产视频一区三区| 在线观看国产日韩| 黄色成人av网站| 麻豆成人精品| 午夜精品久久久久久久久 | 欧美三日本三级三级在线播放| 欧美日本免费一区二区三区| 欧美日本精品| 国内揄拍国内精品久久| 在线看成人片| 黄色成人在线观看| 亚洲人成在线观看网站高清| 亚洲图片欧美一区| 久久精品视频播放| 欧美了一区在线观看| 欧美激情精品久久久久久黑人| 欧美岛国在线观看| 亚洲欧美日韩天堂一区二区| 亚洲精品一区中文| 欧美一区亚洲| 久久精品国产91精品亚洲| 一区二区视频免费在线观看 | 国产亚洲精品成人av久久ww| 国产欧美91| 国产精品毛片高清在线完整版| 国产一区二区三区自拍| 亚洲欧洲日本专区| 欧美一区二区三区男人的天堂| 欧美成人中文字幕在线| 国产精品一区二区女厕厕| 亚洲电影激情视频网站| 亚洲主播在线观看| 欧美大成色www永久网站婷| 国产精品一区在线播放| 亚洲精品美女免费| 久久精品人人做人人综合| 欧美日韩一区二区三区四区在线观看 | 欧美日本网站| 国产亚洲午夜| 亚洲一区免费视频| 欧美精品久久99| 欧美亚洲在线| 在线欧美福利| 亚洲一区二区三区在线| 免费在线日韩av| 欧美午夜不卡影院在线观看完整版免费| 狠狠综合久久| 亚洲国产高潮在线观看| 久久av一区二区三区漫画| 欧美日韩中文字幕精品| 亚洲大片在线| 久久精品亚洲一区| 国产伦精品免费视频| 99精品欧美一区二区三区综合在线 | 久久视频在线视频| 国产精品美女主播在线观看纯欲| ●精品国产综合乱码久久久久| 欧美一区二区三区免费视频| 欧美精品自拍| 亚洲国产三级| 亚洲免费在线观看| 欧美日韩一区在线观看视频| 亚洲成人在线网| 一个人看的www久久| 欧美.日韩.国产.一区.二区| 国产精品美女主播在线观看纯欲| a4yy欧美一区二区三区| 欧美成人第一页| 在线成人激情| 久久国产日韩欧美| 国产欧美一级| 亚欧美中日韩视频| 国产伦精品一区二区三区视频孕妇| 一区二区三区欧美日韩| 欧美了一区在线观看| 亚洲国产精品久久久久秋霞蜜臀 | 欧美人与性动交a欧美精品| 亚洲第一免费播放区| 久久久亚洲精品一区二区三区| 免费精品99久久国产综合精品| 国产亚洲人成a一在线v站| 亚洲免费影视| 亚洲精品女人| 欧美日本久久| 欧美一级大片在线免费观看| 国产一区二区三区久久 | 亚洲日韩欧美一区二区在线| 国产一区二区三区免费不卡| 欧美久久电影| **性色生活片久久毛片| 亚洲欧美综合网| 国产欧美日韩激情| 欧美在线视频二区| 国产午夜亚洲精品不卡| 欧美在线日韩在线| 黄色免费成人| 欧美精品一区二| 99视频日韩| 国产精品久久二区二区| 日韩一级欧洲| 欧美午夜一区二区福利视频| 亚洲在线观看免费视频| 国产一区二区黄色| 久久婷婷亚洲| 激情综合电影网| 免费观看成人| 日韩一级黄色大片| 欧美日韩精品福利| 99精品福利视频| 国产精品电影观看| 日韩午夜av| 国产精品久久久久77777| 亚洲深夜影院| 国产精品乱码一区二三区小蝌蚪 | 午夜在线电影亚洲一区| 国产女主播一区二区三区| 久久久91精品国产一区二区三区| 亚洲精品中文字幕有码专区| 国产精品成人一区二区三区吃奶| 亚洲欧美日韩一区| 黄色亚洲免费| 欧美区国产区| 午夜亚洲福利| 亚洲精品视频在线看| 国产精品蜜臀在线观看| 久热re这里精品视频在线6| 久久久国产精彩视频美女艺术照福利| 欧美性做爰毛片| 久久综合中文字幕| 一本色道久久综合亚洲精品按摩| 国产亚洲精品久久久久婷婷瑜伽| 美女国产精品| 欧美一级片久久久久久久| 18成人免费观看视频| 国产精品视频在线观看| 女生裸体视频一区二区三区 | 99视频有精品| 激情欧美国产欧美| 欧美日韩精品一区二区| 亚洲一区二区久久| 亚洲福利电影| 国产亚洲精品一区二区|