javascript
JavaScript闭包的底层运行机制
轉自:http://blog.leapoahead.com/2015/09/15/js-closure/
我研究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),有時候也稱作 Lexical Environment 。例如,當你調用函數時,函數定義了一些本地變量,這些變量就被存儲在一個作用域對象中。你可以將作用域函數想象成一個普通的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;我們在全局環境中創建了兩個變量。正如我剛才所說,此時的作用域對象就是全局對象。
在上面的代碼中,我們有一個執行的上下文( 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")被運行之前,對象之間的關系是如下圖所示。
溫習一下。myFunc所引用的函數對象其本身不僅僅含有函數的代碼,并且還含有指向其被創建的時候的作用域對象。這一點非常重要!
當myFunc函數被調用的時候,一個新的作用域對象被創建了。新的作用域對象中包含myFunc函數所定義的本地變量,以及其參數(arguments)。這個新的作用域對象的父作用域對象就是在運行myFunc時我們所能直接訪問的那個作用域對象。
所以,當myFunc被執行的時候,對象之間的關系如下圖所示。
現在我們就擁有了一個作用域鏈。當我們試圖在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了。當垃圾回收結束后,對象之間的關系變成回了調用前的關系。
接下來,為了圖表直觀起見,我將不再將函數對象畫出來。但是,請永遠記著,函數對象里面的[[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) 的那一瞬間,對象之間的關系如下圖
注意 increment 和 get 函數都存有指向 createCounter(100) scope 的引用。如果 createCounter(100) 沒有任何返回值,那么 createCounter(100) scope 不再被引用,于是就可以被垃圾回收。但是因為 createCounter(100) 實際上是有返回值的,并且返回值被存儲在了 myCounter 中,所以對象之間的引用關系變成了如下圖所示
所以, createCounter(100) 雖然已經返回了,但是它的作用域對象依然存在,可以 且僅只能 被嵌套的函數( increment 和 get )所訪問。
讓我們試著運行 myCounter.get() 。剛才說過,函數被調用的時候會創建一個新的作用域對象,并且該作用域對象的父作用域對象會是當前可以直接訪問的作用域對象。所以,當 myCounter.get() 被調用時的一瞬間,對象之間的關系如下。
在 myCounter.get() 運行的過程中,作用域鏈最底層的對象就是 get() scope ,這是一個空對象。所以,當 myCounter.get() 訪問 counter 變量時,JavaScript在 get() scope 中找不到這個屬性,于是就向上到 createCounter(100) scope 當中查找。然后, myCounter.get() 將這個值返回。
調用 myCounter.increment(5) 的時候,事情變得更有趣了,因為這個時候函數調用的時候傳入了參數。
正如你所見, increment(5) 的調用創建了一個新的作用域對象,并且其中含有傳入的參數 value 。當這個函數嘗試訪問 value 的時候,JavaScript立刻就能在當前的作用域對象找到它。然而,這個函數試圖訪問 counter 的時候,JavaScript無法在當前的作用域對象找到它,于是就會在其父作用域 createCounter(100) scope 中查找。
我們可以注意到,在 createCounter 函數之外,除了被返回的 get 和 increment 兩個方法,沒有其他的地方可以訪問到 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);當 myCounter1 和 myCounter2 被創建后,對象之間的關系為
在上面的例子中, myCounter1.increment 和 myCounter2.increment 的函數對象擁有著一樣的代碼以及一樣的屬性值( name , length 等等),但是它們的 [[scope]] 指向的是 不一樣的作用域對象 。
這才有了下面的結果
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 的值取決于函數被調用的時候的情景。
譯者注:對這部分,譯者自己曾經寫過一篇更加詳盡的文章,請參考 《用自然語言的角度理解JavaScript中的this關鍵字》 。原文的這一部分以及“ this 在嵌套的函數中的使用”譯者便不再翻譯。
總結
讓我們來回想我們在本文開頭提到的一些問題。
- 什么是閉包?閉包就是同時含有對函數對象以及作用域對象引用的最想。實際上,所有JavaScript對象都是閉包。
- 閉包是什么時候被創建的?因為所有JavaScript對象都是閉包,因此,當你定義一個函數的時候,你就定義了一個閉包。
- 閉包是什么時候被銷毀的?當它不被任何其他的對象引用的時候。
專有名詞翻譯表
本文采用下面的專有名詞翻譯表,如有更好的翻譯請告知,尤其是加 * 的翻譯
- *全局環境中運行的代碼:top-level code
- 參數:arguments
- 作用域對象:Scope object
- 作用域鏈:Scope Chain
- 棧:stack
- 原型繼承:prototypal inheritance
- 原型鏈:prototype chain
- 全局對象:Global Object
- 標識符:identifier
- 垃圾回收器:garbage collector
著作權聲明
本文經授權翻譯自 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.jsWe 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.jsWhen 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.jsWhen 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 “live”. 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.jsWhen 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 205So, 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 100If 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 undefinedWhen 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.
轉載于:https://www.cnblogs.com/digdeep/p/4817141.html
總結
以上是生活随笔為你收集整理的JavaScript闭包的底层运行机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 魔兽世界---屠夫(Just a Hoo
- 下一篇: Linux bind-utils