Table of Contents
How JavaScript closures work under the hood
You're reading the original article in English. You can as well read the translations:
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 “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.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
isundefined
- 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:
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.
Discuss on reddit: or on Hacker News: Vote on HN
Discussion
Thanks for the great article. Really helpful for me.
A little question: what software do you use to draw these diagrams?
Glad it helped.
As to diagram-drawing software, it's yEd. Not that I'm completely satisfied with it, but it's the best of what I've came up with. Take a look at PlantUML as well, I use it sometimes and I really like it for its text sources. (and if it had supported links to specific member of the object, not just to the whole object, I would've used PlantUML for this article as well)
The best explanation of closures I've read. Thank you.
You're welcome, Andrew!
Thanks for this post, it's very interesting. High Performance JavaScript by Nicholas C. Zakas has some great examples and illustrations in Chapter 2, have you checked those out?
Scott, I have this book on my to-read list, but haven't read it yet. So will probably get to it when I finish The Definitive Guide. Thanks for mentioning it!
Good article. It would be interesting to add examples if you take a function that has closure and use function.prototype.call() to provide a this and show that may change the scope chain.
Hi Patrick, thanks for your feedback.
Regarding to
Function.prototype.call()
, it's a bit misleading to say that providing customthis
will change the scope chain: actually,this
is not saved as part of the scope chain at all.Thanks for the suggestion, this topic is definitely worth explaining, so, I've added new section in the article: Scope chain and "this".
Hey, Really good Article . But i have a question let's say the increment function inside has some locally defined variables so when we call myCounter.increment() then what is the scope of that variables after function execution will they be garbage collected ?
Hi Priyank, thanks for your feedback.
When we call
myCounter.increment()
, we have the scope chain as in this diagram, just replaceget
withincrement
.So, if
increment()
has some local variables, like this:Then, variable
myLocalVar
will be stored in the left scope (which is empty in the diagram). And yes, whenincrement()
returns, the scope withmyLocalVar
will be garbage-collected.Note that particular implementations of JavaScript may optimize empty scopes away (as well as scopes which contain only unused variables), but the idea is still here.
Priyank, I've updated the article: now, for clarity, the function
increment()
takes an argument. So, the diagram now looks as follows, and you might want to re-read the section Nested functions.Thanks for your question, it helped me to understand what should be improved.
Thank you for such a great article on JS Closures. One clarification - Won't myCounter1.increment and myCounter2.increment refer to different function objects as the function increment is defined when createCounter is called?
Hello Sharat! Thank you very much for your comment. Although I did my best to re-check myself before publishing, I finally made a mistake. :( Yes, you're right; of course, the function objects, while having exactly the same code, will be two different objects. How could I miss that..
Thanks again, I will fix the article.
Loved reading this article, thanks! However this part confuses me:
How can a single reference references two objects? According to the specification (step 9) scope is stored on the function object itself, so this diagram makes more sense:
What do you think?
Thank you very much for your valuable feedback!
Well, my understanding was that there might be some intermediary object that references function object and scope object, but now it's obvious that I was wrong in that: of course, the function object itself references scope object. Thanks for pointing this out.
No problem, really great article! Got me thinking and digging in the spec.
Also the difference between outer and inner scope kept me busy for a while. I believe the outer scope is the scope in which the function is defined (referenced as [[scope]]) and the inner scope is a new scope created every time the function is called (meaning there can be many inner scopes).
Great article.
So the function object's reference to its scope is used to construct the scope chain?
So in your third image, the [[scope]] pointer is copied to create the left most white-headed arrow?
Thank you for your feedback.
Right.
Yes, we can see it in this way. In UML, the white-headed arrow is used to denote the inheritance, so, that's what I tried to show. So, when the function is called, new scope object is created (the leftmost one on the diagram), and it inherits from the scope object pointed to by function's [[scope]].
very visually helpful looking forward to follow up article with ES6 let and arrow functions?
Rolograaf, thank for the feedback. Sorry for the late reply, I was outdoors for several days.
I was already thinking of expanding the article with
let
from ES6. I hope I'll have some time for it. As to arrow functions, as far as I know, they're just a syntactic sugar (even though an appealing one), so, they don't bring anything new to the “under the hood” picture of scopes.Hi, I enjoyed the article. I think you should add
initial
to thecreateCounter()
scopes. I was slightly confused when you hadvalue
in theincrement
scope. I hadn't realized at first that arguments would be in the scope; but of course they would,initial
is still accessible toget()
andincrement()
even though it's not used.Hi Chad, I'm glad you enjoyed!
And yes, you're right; I've added
initial
to diagrams. Thanks for pointing this out.Thanks for the clear and nice explanation. Special thanks for including the “This” also in the article. The article clearly explains two most misunderstood concepts this and closures are not related to one other.
You're welcome! It's always good to get some positive feedback, thanks!
It's “clarity”, not “clearness”.
Ok, thanks. Noted. I'm wondering whether it is the most critical English mistake I've made (and even not in the article, but in comment)
Dude much appreciated for the article. Charlie
You're welcome, Charlie! And thanks for your kind feedback.
Great article. I finally understand how closures work in JavaScript and also what 'this' means. Thanks a lot for your effort :)
You're welcome. Glad it helped :)
What an excellent write up. I'll need to revisit to read it again! :)
Thanks!
This article is super!
I thought i totally understand the lexical scope stuff. After reading this, I found I actually had many missing corners.
Thanks for sharing.
You're welcome, Leon, glad you liked!
Hello, thank you for a very useful article.
I've translate it to Vietnamese at https://viblo.asia/xuanduc987/posts/lA7GKnWWMKZQ
Hello, thanks, that's great! I updated the list at the top.
Great article!非常棒,感谢博主!
Thanks for the comment :)
Thank you Dmitry,
This was a great read.
I am however having a difficult time understanding
https://www.toptal.com/javascript/10-most-common-javascript-mistakes :Common Mistake #3: Creating memory leaks
With this layout, any change you could elaborate?
Thank Dmitry. Great article!
And thanks for the feedback!
I have created deep nested functions. There are four nested functions function four() is nested → inside function three() nested → inside function two() nested → inside function one(). The Code -
function one() {
}
Run code in Chrome specifically to check Scopes Other browser like Firefox does not show Scopes
console.dir(one); console.dir(one()); console.dir(one()()); console.dir(one()()());
So when I run the code and inspect the results in Chrome Developer tools I observed closures in Scopes were not only created for function four but also for function three and function two. It looks as if the local variables that were out of that function scope kept moving out of its functional scope and continued creating closures for each function until it did not found the variable.
So what is happening under the hood exactly ?
Now I am not sure if this behavior is related JavaScript or is it something to do with The Chrome Dev tools.
Excuse for any mistakes
Hoping for a reply soon :)
Thank you Dmitry,
Nice article!
Such a great article. You made such a misunderstood concept easy to understand. Hats off! Please provide a like button on your page or comments section might get flooded by appreciations only and doubts from people would get unresolved.
Thanks for the positive feedback!
Thank you for your great article. The closures is very hard for me. This article help me to clearly