For those of you who are writing code in Javascript or wish to do so, it is essential to understand
the concept of closures, and how they work.
I must say that when I started looking into it, it was somewhat difficult to find proper material
on the web, to get a good grasp of how things work. So this post's idea is to try and shed some
light on this matter. I don't guarantee that this will make you understand everything, but do hope
it will help in some way.
*Note: I could have some mistakes in this post of course, and would be very happy if you send me
any corrections or comment as this is suppose to help people, and show the power of Javascript closures.
Everything that's written in this post is according to my understanding of how things work.
So regard this writing with care. I tried to do the best I could when I wrote this post.
The post topics will contain 2 parts:
1. What is a Closure?
2. How does the memory model look like.
1. What is a Closure?
"... is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables) of that function. A closure— unlike a plain function pointer—allows a function to access those non-local variables even when invoked outside of its immediate lexical scope." - From Wikipedia.A simple example to such Closure is a counter.
Consider the following script for example:
function createCounter() {
var counter = 0 ;
return function() {
counter++ ;
return counter ;
}
}
var counter = createCounter() ;
counter() ; // returns 1
counter() ; // returns 2
var counter2 = createCounter() ;
counter2() ; // returns 1
counter() ; // returns 3
How does this work?
The createCounter function being executed creates a new Execution Scope, which contains references to a variable named counter and returns an anonymous function.
So how does that function know about counter when it's executed?
In Javascript any function, has some kind of property called a "Scope Chain", which defines the scopes the function is bounded to. When we define a function in Javascript, the "definition scope" is such scope that is stored in the function's "Scope chain". So when we define and return the anonymous function in createCounter that anonymous function is binded to a scope where it was defined.
Thus, when the function is being executed by calling "counter()" a new "execution scope" is being created and added to the function's scope chain. So when the function needs to look up counter it looks up it's "scope-chain", and finds it eventually on the "definition scope" of the function.
Another thing to remember is that every time you run a function you run it inside a "context".
What is a context?
A context is the object the function is executed on. It is recognized with the this keyword.
It is defined when we write the following code in Javascript for example:
// context here equals to obj1 (this === obj1)
obj1.sayHello() ;
// context here equals to the global object which in case of a browser, is *window (this === window)
sayHello() ;
* It might be equal to undefined (this === undefined) in case we're running in 'strict mode'.
You can check more about it at MDN - Function Context.* It might be equal to undefined (this === undefined) in case we're running in 'strict mode'.
2. How does the memory model look like?
I think it's important to understand how the memory model looks like, graphically, to get a better
notion of how things work. Even though you could handle without it, I find illustrations very
helpful understanding how something works.
Simple example
When you define a regular function, like:
function foo(number) {
[ function_body ]
}
We get something of the following in memory:
If we execute foo(3), we will get something of the following in memory:
So when foo(3) is executed, a new "Activation object" is created.
Passing to it the function's parameters and arguments array, and adding it to the scope chain of foo(3).
So the next time foo will be looking up for the "num" parameter, it will be looking it up the
scope chain starting from the closest(last) scope that is relevant to the execution, which in this
case is of course the "Activation". If it won't find num there, it will continue to the next scope in the
chain, until it finds it.
Closure example
Lets take the Closure example we referred to earlier - the counter function and see how it
would look like in memory.
Quick reminder, our counter function looks like this:
This maps to a memory model which looks (somewhat) like this:
You might wonder why the Closure's scope chain also refers to the Global Object.
The reason is that when we create a closure, it copies the scope chain of the execution context scope chain which in this case belongs to createCounter().
What makes the Closure a closure is the interesting side affect in Javascript, that instead of the Activation Object being destroyed after createCounter execution, it remains alive, because there is a reference to it, from another function - the closure.
Now, lets create such counter by executing the following:
So on line [1] we create myCounter which is a reference to the closure on the above illustration.
When we execute line [2], we get 1 as an answer, and the second time we will get 2.
How it looks in the memory is shown in the next illustration:
function createCounter() {
var counter = 0 ;
return function() {
counter++ ;
return counter ;
}
}
This maps to a memory model which looks (somewhat) like this:
You might wonder why the Closure's scope chain also refers to the Global Object.
The reason is that when we create a closure, it copies the scope chain of the execution context scope chain which in this case belongs to createCounter().
What makes the Closure a closure is the interesting side affect in Javascript, that instead of the Activation Object being destroyed after createCounter execution, it remains alive, because there is a reference to it, from another function - the closure.
Now, lets create such counter by executing the following:
var myCounter = createCounter(); [1]
myCounter() ; [2]
So on line [1] we create myCounter which is a reference to the closure on the above illustration.
When we execute line [2], we get 1 as an answer, and the second time we will get 2.
How it looks in the memory is shown in the next illustration:
When we execute myCounter which is our closure in this case, we are incrementing the counter variable, but in order to do so, the variable needs to be reached somehow.
This is where the "scope chain" takes its place. The first "environment" where the variable is going to be looked up is at the top of the scope chain, in our case - the "Activation Object" of myCounter.
Since the variable is not an inner variable, nor a parameter, the look-up for the variable continues, and moves on to the next scope in the chain which is the "Activation Object" of createCounter ! There it is found, read and assigned. (As counter++ is actually counter = counter + 1 ).
Note that every time you call createCounter a new Activation Object is created with a new counter variable which equals to 0. This is how you can create as many separate counters as you like, without them affecting each other.
I hope this helped you in some way to understand the memory model of functions and closures a little better. Any comments / suggestions and complaints are most welcome.
Enjoy "closuring".
No comments:
Post a Comment