日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript闭包的底层运行机制

發(fā)布時間:2024/4/17 javascript 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript闭包的底层运行机制 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)自:http://blog.leapoahead.com/2015/09/15/js-closure/

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

Wikipedia 給出的解釋并沒有太大的幫助。閉包是什么時候被創(chuàng)建的,什么時候被銷毀的?具體的實現(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 // 相信對JS熟悉的朋友都能很快理解這段代碼 // 那么在這段代碼運行的背后究竟發(fā)生了怎樣的事情呢?

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

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

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

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

作用域鏈(Scope Chain)

當(dāng)JavaScript在運行的時候,它需要一些空間讓它來存儲本地變量(local variables)。我們將這些空間稱為作用域?qū)ο?#xff08;Scope object),有時候也稱作 Lexical Environment 。例如,當(dāng)你調(diào)用函數(shù)時,函數(shù)定義了一些本地變量,這些變量就被存儲在一個作用域?qū)ο笾小D憧梢詫⒆饔糜蚝瘮?shù)想象成一個普通的JavaScript對象, 但是有一個很大的區(qū)別就是你不能夠直接在JavaScript當(dāng)中直接獲取這個對象。你只可以修改這個對象的屬性,但是你不能夠獲取這個對象的引用。

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

正如你做想的,作用域?qū)ο笫强梢杂懈缸饔糜驅(qū)ο?#xff08;parent scope object)的。當(dāng)代碼試圖訪問一個變量的時候,解釋器將在當(dāng)前的作用域?qū)ο笾胁檎疫@個屬性。如果這個屬性不存在,那么解釋器就會在父作用域?qū)ο笾胁檎?這個屬性。就這樣,一直向父作用域?qū)ο蟛檎?#xff0c;直到找到該屬性或者再也沒有父作用域?qū)ο蟆N覀儗⑦@個查找變量的過程中所經(jīng)過的作用域?qū)ο蟪俗饔糜蜴?(Scope chain)。

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

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

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

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

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

我們在全局環(huán)境中創(chuàng)建了兩個變量。正如我剛才所說,此時的作用域?qū)ο缶褪侨謱ο蟆?/p>

在上面的代碼中,我們有一個執(zhí)行的上下文( myscript.js 自身的代碼),以及它所引用的作用域?qū)ο蟆H謱ο罄锩孢€含有很多不同的屬性,在這里我們就忽略掉了。

沒有被嵌套的函數(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 被定義的時候, myFunc 的標(biāo)識符(identifier)就被加到了當(dāng)前的作用域?qū)ο笾?#xff08;在這里就是全局對象),并且這個標(biāo)識符所引用的是一個函數(shù)對象(function object)。函數(shù)對象中所包含的是函數(shù)的源代碼以及其他的屬性。其中一個我們所關(guān)心的屬性就是內(nèi)部屬性 [[scope]] 。 [[scope]] 所指向的就是當(dāng)前的作用域?qū)ο蟆R簿褪侵傅木褪呛瘮?shù)的標(biāo)識符被創(chuàng)建的時候,我們所能夠直接訪問的那個作用域?qū)ο?#xff08;在這里就是全局對象)。

“直接訪問”的意思就是,在當(dāng)前作用域鏈中,該作用域?qū)ο筇幱谧畹讓?#xff0c;沒有子作用域?qū)ο蟆?/p>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

如果我們將這個嵌套函數(shù)返回,并被另外一個地方的標(biāo)識符所引用的話,那么這個嵌套函數(shù)及其 [[scope]] 所引用的作用域?qū)ο缶筒粫焕厥账N毀。

"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) 的那一瞬間,對象之間的關(guān)系如下圖


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

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

讓我們試著運行 myCounter.get() 。剛才說過,函數(shù)被調(diào)用的時候會創(chuàng)建一個新的作用域?qū)ο?#xff0c;并且該作用域?qū)ο蟮母缸饔糜驅(qū)ο髸钱?dāng)前可以直接訪問的作用域?qū)ο蟆K?#xff0c;當(dāng) myCounter.get() 被調(diào)用時的一瞬間,對象之間的關(guān)系如下。

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

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

正如你所見, increment(5) 的調(diào)用創(chuàng)建了一個新的作用域?qū)ο?#xff0c;并且其中含有傳入的參數(shù) value 。當(dāng)這個函數(shù)嘗試訪問 value 的時候,JavaScript立刻就能在當(dāng)前的作用域?qū)ο笳业剿H欢?#xff0c;這個函數(shù)試圖訪問 counter 的時候,JavaScript無法在當(dāng)前的作用域?qū)ο笳业剿?#xff0c;于是就會在其父作用域 createCounter(100) scope 中查找。

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

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

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

這也就是為什么下面這個大家都很熟悉的例子會不能產(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)建了多個函數(shù)對象,所有的函數(shù)對象的 [[scope]] 都保存著對當(dāng)前作用域?qū)ο蟮囊谩6兞?i 正好就在當(dāng)前作用域鏈中,所以循環(huán)每次對 i 的修改,對于每個函數(shù)對象都是能夠看到的。

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

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

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

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

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

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

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

作用域鏈和 this

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

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

總結(jié)

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

  • 什么是閉包?閉包就是同時含有對函數(shù)對象以及作用域?qū)ο笠玫淖钕搿嶋H上,所有JavaScript對象都是閉包。
  • 閉包是什么時候被創(chuàng)建的?因為所有JavaScript對象都是閉包,因此,當(dāng)你定義一個函數(shù)的時候,你就定義了一個閉包。
  • 閉包是什么時候被銷毀的?當(dāng)它不被任何其他的對象引用的時候。

專有名詞翻譯表

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

  • *全局環(huán)境中運行的代碼:top-level code
  • 參數(shù):arguments
  • 作用域?qū)ο?#xff1a;Scope object
  • 作用域鏈:Scope Chain
  • 棧:stack
  • 原型繼承:prototypal inheritance
  • 原型鏈:prototype chain
  • 全局對象:Global Object
  • 標(biāo)識符:identifier
  • 垃圾回收器:garbage collector

著作權(quán)聲明

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

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

-----------------------------------------------------------附上原文----------------------------------------------------

http://dmitryfrank.com/articles/js_closures

How do JavaScript closures work under the hood

You're reading the original article in English. You can as well read the translation in Russian.

If you have translated the article to different language, please leave a comment or write me an email, so that I can update the list. Thank you.


I've been using closures for quite some time already. I learned how to use them, but I didn't have clear understanding of how closures actually work, and what's going on behind the scenes when I use them. What the closure is exactly, to begin with? Wikipedia doesn't help very much. When it is created and deleted? What the implementation should look like?

"use strict";var myClosure = (function outerFunction() { ? var hidden = 1; ? return { inc: function innerFunction() { return hidden++; } }; ? }()); ? myClosure.inc(); // returns 1 myClosure.inc(); // returns 2 myClosure.inc(); // returns 3 ? // Ok, very nice. But how is it implemented, // and what's going on behind the scenes?

And when I eventually got it, I felt excited and decided to explain it: at least, I will definitely not forget it now. You know,

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

And, after all, while I was reading existing explanations of closures, I tried hard to imagine visually how things relate to each other: which object references others, which one inherits from another, etc. I failed to find such useful illustrations, so, I decided to draw my own.

I assume that the reader is already familiar with JavaScript, knows what is a Global Object, knows that functions in JavaScript are “first-class objects”, etc.

Scope chain

When any JavaScript code is executing, it needs some place to store its local variables. Let's call this place as a scope object (some refer to it as a LexicalEnvironment). For example, when you invoke some function, and function defines local variables, these variables are saved on the scope object. You can think of it as a regular JavaScript object, with the notable difference that you can't refer to the whole object directly. You can only modify its properties, but you can't refer to the scope object itself.

This concept of scope object is very different from, say, C or C++, where local variables are stored on stack. In JavaScript, scope objects are allocated in heap instead (or at least they behave like this), so they might stay allocated even if function already returned. More on that later.

As you might expect, scope object might have parent. When the code tries to access some variable, interpreter looks for the property of current scope object. If the property doesn't exist, interpreter moves to the parent scope object, and looks there. And so on, until the value is found, or there's no more parent. Let's call this sequence of scope objects as a scope chain.

The behavior of resolving a variable on scope chain is very similar to that of prototypal inheritance, with, again, one notable difference: if you try to access some non-existing property of regular object, and prototype chain doesn't contain this property either, it's not an error: undefined is silently returned. But if you try to access non-existing property on the scope chain (i.e. access non-existing variable), then ReferenceError occurs.

The last element in the scope chain is always the Global Object. In top-level JavaScript code, scope chain contains just a single element: the Global Object. So, when you define some variables in top-level code, they are defined on Global Object. When some function is invoked, scope chain consists of more than one object. You might expect that if function is called from top-level code, then scope chain is guaranteed to contain exactly 2 scope objects, but this is not necessarily true! There might be 2 or more scope objects; it depends on the function. More on that later.

Top-level code

Ok, enough theory, let's try something concrete. Here is a very simple example:

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

We just create two variables in top-level code. As I mentioned above, for top-level code, scope object is a Global Object:

In the diagram above, we have execution context (which is just top-level my_script.js code), and the scope object referenced by it. Of course, in real world, Global Object contains lots of standard- and host-specific stuff, but it's not reflected in the diagrams.

Non-nested functions

Now, consider this script:

my_script.js
"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();

When the function myFunc is defined, myFunc identifier is added to the current scope object (in this case, Global object), and this identifier refers to the function object. The function object holds function's code as well as other properties. One of the properties that we're interested in is an internal property [[scope]]; it refers to the current scope object, that is, the scope object that was active when the function is defined (again, in this case, Global object).

So, by the time the console.log(“outside”); is executed, we have the following arrangement:

Again, take a moment to reflect on it: the function object referred by myFunc variable not only holds the function code, but also refers to the scope object which was in effect when the function is defined. This is very important.

And when the function is invoked, new scope object is created, which keeps local variables for myFunc (as well as its argument values), and this new scope object inherits from the scope object referenced by the function being invoked.

So, when myFunc is actually invoked, the arrangement becomes as follows:

What we have now is a scope chain: if we try to access some variable inside myFunc, JavaScript will try to find it on the first scope object: myFunc() scope. If the lookup fails, then go to the next scope object in the chain (here, it's Global object), and look there. If requested property is not a part of all scope objects in the chain, then ReferenceError occurs.

For example, if we access a from myFunc, we get value 1 from the first scope object myFunc() scope. If we access foo, we get value 3 from the same myFunc() scope: it effectively hides the foo property of the Global object. If we access bar, we get value 2 from Global object. It works pretty much like prototypal inheritance.

It's important to note that these scope objects are persisted as long as there are references to them. When the last reference to some particular scope object is dropped, this scope object will be garbage-collected on occasion.

So, when myFunc() returns, nobody references myFunc() scope anymore, it gets garbage-collected, and we end up with previous arrangement again:

From now on, I won't include explicit function objects in the diagrams, since diagrams become too overloaded otherwise. You already know: any reference to a function in JavaScript refers to function object, which, in turn, has a reference to the scope object.

Always keep this in mind.

Nested functions

As we've seen from the previous discussion, when function returns, nobody else references its scope object, and so, it gets garbage-collected. But what if we define nested function and return it (or store somewhere outside)? You already know: function object always refers to the scope object in which it was created. So, when we define nested function, it gets reference to the current scope object of outer function. And if we store that nested function somewhere outside, then the scope object won't be garbage collected even after outer function returns: there are still references to it! Consider this code:

my_script.js
"use strict";function createCounter(initial) { //-- define local-to-function variables var counter = initial; ? //-- define nested functions. Each of them will have // a reference to the current scope object ? /** * Increments internal counter by given value. * If given value is not a finite number or is less than 1, then 1 is used. */ function increment(value) { if (!isFinite(value) || value < 1){ value = 1; } counter += value; } ? /** * Returns current counter value. */ function get() { return counter; } ? ? //-- return object containing references // to nested functions return { increment: increment, get: get }; } ? //-- create counter object var myCounter = createCounter(100); ? console.log(myCounter.get()); //-- prints "100" ? myCounter.increment(5); console.log(myCounter.get()); //-- prints "105"

When we call createCounter(100);, we have the following arrangement:

Notice that createCounter(100) scope is referenced by nested functions increment and get. If createCounter() returned nothing, then, of course, these inner self-references wouldn't be counted, and the scope would be garbage-collected anyway. But since createCounter() returns object containing references to these functions, we have the following:

Take some time to reflect on it: the function createCounter(100) already returned, but its scope is still there, accessible by the inner functions, and only by these functions. It is truly impossible to access createCounter(100) scope object directly, we can only call myCounter.increment() or myCounter.get(). These functions have unique private access to the scope of createCounter.

Let's try to call, for example, myCounter.get(). Recall that when any function is called, new scope object is created, and scope chain that is referenced by the function is augmented with this new scope object. So, when myCounter.get() is called, what we have is:

The first scope object in the chain for function get() is the empty object get() scope. So, when get() accesses counter variable, JavaScript fails to find it on the first object in the scope chain, moves to the next scope object, and uses counter variable on createCounter(100) scope. And get() function just returns it.

You may have noticed that myCounter object is additionally given to the function myCounter.get() as this (denoted by red arrow on the diagram). This is because this is never a part of the scope chain, and you should be aware of it. More on that later.

Calling increment(5) is a bit more interesting, since this function has an argument:

As you see, the argument value is stored in the scope object that was created just for a single call increment(5). When this function accesses variable value, JavaScript immediately locates it on the first object in the scope chain. When, however, the function accesses counter, JavaScript fails to find it on the first object in the scope chain, moves to the next scope object, and locates it there. So, increment() modifies the counter variable on createCounter(100) scope. And virtually nobody else can modify this variable. This is why closures are so powerful: the myCounter object is impossible to compromise. Closures are very appropriate place to store private things.

Notice that the argument initial is also stored in the scope object of createCounter(), even though it is not used. So, we can save a bit of memory if we get rid of explicit var counter = initial;, rename initial to counter, and use it directly. But, for clarity, we have explicit argument initial and var counter.

It is important to highlight that bound scopes are “l(fā)ive”. When function is invoked, current scope chain is not copied for this function: the scope chain is just augmented with new scope object, and when any scope object in the chain is modified by any function, this change is immediately observable by all functions that have this scope object in their scope chains. When increment() modifies counter value, the next call to get() will return updated value.

This is why this well-known example doesn't work:

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

Multiple functions are created in the loop, and all of them have reference to the same scope object in their scope chains. So, they use exactly the same variable i, not private copies of it. For further explanation of this particular example, see this link: Don't make functions within a loop.

Similar function objects, different scope objects

Now, let's try to expand a bit our counter example and have more fun. What if we create more than one counter objects? It doesn't hurt:

my_script.js
"use strict";function createCounter(initial) { /* ... see the code from previous example ... */ } ? //-- create counter objects var myCounter1 = createCounter(100); var myCounter2 = createCounter(200);

When both myCounter1 and myCounter2 are created, we have the following:

Be sure to remember that each function object has a reference to the scope object. So, in the example above, myCounter1.increment and myCounter2.increment refer to function objects that have exactly the same code and the same property values (name, length, and others), but their [[scope]] refer to different scope objects.

Diagram does not contain separate function objects, for the sake of simplicity, but they are still there.

Some examples:

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

So, this is how it works. The concept of closures is very powerful.

Scope chain and "this"

Like it or not, this is not saved as a part of the scope chain at all. Instead, value of this depends on the function invocation pattern: that is, you may call the same function with different values given as this.

Invocation patterns

This topic pretty much deserves its own article, so I won't go deeply inside, but as a quick overview, there are four invocation patterns. Here we go:

Method invocation pattern

"use strict";var myObj = { myProp: 100, myFunc: function myFunc() { return this.myProp; } }; myObj.myFunc(); //-- returned 100

If an invocation expression contains a refinement (a dot, or [subscript]), the function is invoked as a method. So, in the example above, this given to myFunc() is a reference to myObj.

Function invocation pattern

"use strict";function myFunc() { return this; } myFunc(); //-- returns undefined

When there's no refinement, then it depends on whether the code runs in strict mode:

  • in strict mode, this is undefined
  • in non-strict mode, this points to Global Object

Since the code above runs in strict mode thanks to “use strict”;, myFunc() returns undefined.

Constructor invocation pattern

"use strict";function MyObj() { this.a = 'a'; this.b = 'b'; } var myObj = new MyObj();

When function is called with new prefix, JavaScripts allocates new object which inherits from the function's prototype property, and this newly allocated object is given to the function as this.

Apply invocation pattern

"use strict";function myFunc(myArg) { return this.myProp + " " + myArg; } ? var result = myFunc.apply( { myProp: "prop" }, [ "arg" ] ); //-- result is "prop arg"

We can pass arbitrary value as this. In the example above, we use Function.prototype.apply() for that. Beyond that, see also:

  • Function.prototype.call()
  • Function.prototype.bind()

In the examples that follow, we will primarily use Method invocation pattern.

Usage of "this" in nested functions

Consider:

"use strict";var myObj = { ? myProp: "outer-value", createInnerObj: function createInnerObj() { ? var hidden = "value-in-closure"; ? return { myProp: "inner-value", innerFunc: function innerFunc() { return "hidden: '" + hidden + "', myProp: '" + this.myProp + "'"; } }; ? } }; ? var myInnerObj = myObj.createInnerObj(); console.log( myInnerObj.innerFunc() );

Prints: hidden: 'value-in-closure', myProp: 'inner-value'

By the time myObj.createInnerObj() is called, we have the following arrangement:

And when we call myInnerObj.innerFunc(), it looks as follows:

From the above, it's clear that this given to myObj.createInnerObj() points to myObj, but this given to myInnerObj.innerFunc() points to myInnerObj: both functions are called with Method invocation pattern, as explained above. That's why this.myProp inside innerFunc() evaluates to “inner-value”, not “outer-value”.

So, we can easily trick innerFunc() into using different myProp, like this:

/* ... see the definition of myObj above ... */var myInnerObj = myObj.createInnerObj(); var fakeObject = { myProp: "fake-inner-value", innerFunc: myInnerObj.innerFunc }; console.log( fakeObject.innerFunc() );

Prints: hidden: 'value-in-closure', myProp: 'fake-inner-value'

Or with apply() or call():

/* ... see the definition of myObj above ... */var myInnerObj = myObj.createInnerObj(); console.log( myInnerObj.innerFunc.call( { myProp: "fake-inner-value-2", } ) );

Prints: hidden: 'value-in-closure', myProp: 'fake-inner-value-2'

Sometimes, however, inner function actually needs access to this given to outer function, independently of the way inner function is invoked. There is a common idiom for that: we need to explicitly save needed value in the closure (that is, in the current scope object), like: var self = this;, and use self in inner function, instead of this. Consider:

"use strict";var myObj = { ? myProp: "outer-value", createInnerObj: function createInnerObj() { ? var self = this; var hidden = "value-in-closure"; ? return { myProp: "inner-value", innerFunc: function innerFunc() { return "hidden: '" + hidden + "', myProp: '" + self.myProp + "'"; } }; ? } }; ? var myInnerObj = myObj.createInnerObj(); console.log( myInnerObj.innerFunc() );

Prints: hidden: 'value-in-closure', myProp: 'outer-value'

This way, we have the following:

As you see, this time, innerFunc() has access to the value given as this to the outer function, through the self stored in the closure.

Conclusion

Let's answer a couple of questions from the beginning of the article:

  • What the closure is? - It is the object that refers both to function object and the scope object. Actually, all JavaScript functions are closures: it's impossible to have the reference to function object without scope object.
  • When is it created? - Since all JavaScript functions are closures, it's obvious: when you define a function, you actually define a closure. So, it is created when the function is defined. But make sure you distinguish between closure creation and new scope object creation: the closure (function + reference to the current scope chain) is created when the function is defined, but new scope object is created (and used to augment the closure's scope chain) when the function is invoked.
  • When is it deleted? - Just like any regular object in JavaScript, it is garbage-collected when there are no references to it.

Further reading:

  • JavaScript: The Good Parts by Douglas Crockford. It's surely nice to understand how do closures work, but it's probably even more important to understand how to use them correctly. The book is very concise, and it contains lots of great and useful patterns.
  • JavaScript: The Definitive Guide by David Flanagan. As the title suggests, this book explains the language in a very detailed way.

轉(zhuǎn)載于:https://www.cnblogs.com/digdeep/p/4817141.html

總結(jié)

以上是生活随笔為你收集整理的JavaScript闭包的底层运行机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。