Sunday 3 December 2017

function - How do JavaScript closures work?





How would you explain JavaScript closures to someone with a
knowledge of the concepts they consist of (for example functions, variables and the
like), but does not understand closures
themselves?



I have seen href="http://en.wikipedia.org/wiki/Scheme_%28programming_language%29"
rel="noreferrer">the Scheme example given on Wikipedia, but unfortunately
it did not help.


itemprop="text">
class="normal">Answer






Submitted by
Morris on Tue, 2006-02-21 10:19. Community-edited
since.


Closures are not
magic



This page explains closures so that a
programmer can understand them — using working JavaScript code. It is not for gurus or
functional programmers.



Closures are
not hard to understand once the core concept is grokked. However,
they are impossible to understand by reading any theoretical or academically oriented
explanations!



This article is intended for
programmers with some programming experience in a mainstream language, and who can read
the following JavaScript function:



class="snippet" data-lang="js" data-hide="false" data-console="true"
data-babel="false">

class="snippet-code-js lang-js prettyprint-override">function
sayHello(name) {
var text = 'Hello ' + name;
var say = function()
{ console.log(text); }

say();
}
sayHello('Joe');





Two
brief
summaries




  • When a
    function (foo) declares other functions (bar and baz), the
    family of local variables created in foo is not
    destroyed
    when the function exits. The variables merely become invisible to
    the outside world. foo can therefore cunningly return the
    functions bar and baz, and they can
    continue to read, write and communicate with each other through this closed-off family
    of variables ("the closure") that nobody else can meddle with, not even someone who
    calls foo again in
    future.


  • A closure is one way of
    supporting rel="noreferrer">first-class functions; it is an expression that can
    reference variables within its scope (when it was first declared), be assigned to a
    variable, be passed as an argument to a function, or be returned as a function result.




An example of
a closure



The following code returns a
reference to a function:



data-lang="js" data-hide="false" data-console="true"
data-babel="false">

class="snippet-code-js lang-js prettyprint-override">function
sayHello2(name) {
var text = 'Hello ' + name; // Local variable

var say = function() { console.log(text); }
return
say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello
Bob"





Most
JavaScript programmers will understand how a reference to a function is returned to a
variable (say2) in the above code. If you don't, then you need
to look at that before you can learn closures. A programmer using C would think of the
function as returning a pointer to a function, and that the variables
say and say2 were each a pointer to a
function.



There is a critical difference between
a C pointer to a function and a JavaScript reference to a function. In JavaScript, you
can think of a function reference variable as having both a pointer to a function
as well as a hidden pointer to a
closure.



The above code has a closure because
the anonymous function function() { console.log(text); } is
declared inside another function,
sayHello2() in this example. In JavaScript, if you use the
function keyword inside another function, you are creating a
closure.



In C and most other common languages,
after a function returns, all the local variables are no longer
accessible because the stack-frame is
destroyed.



In JavaScript, if you declare a
function within another function, then the local variables of the outer function can
remain accessible after returning from it. This is demonstrated above, because we call
the function say2() after we have returned from
sayHello2(). Notice that the code that we call references the
variable text, which was a local variable
of the function
sayHello2().



function()
{ console.log(text); } // Output of
say2.toString();


Looking
at the output of say2.toString(), we can see that the code
refers to the variable text. The anonymous function can
reference text which holds the value 'Hello
Bob'
because the local variables of sayHello2()
have been secretly kept alive in a closure.



The
genius is that in JavaScript a function reference also has a secret reference to the
closure it was created in — similar to how delegates are a method pointer plus a secret
reference to an object.



More
examples



For some reason, closures seem really
hard to understand when you read about them, but when you see some examples, it becomes
clear how they work (it took me a while).
I recommend working through the
examples carefully until you understand how they work. If you start using closures
without fully understanding how they work, you would soon create some very weird
bugs!



Example
3



This example shows that the local variables
are not copied — they are kept by reference. It is as though the stack-frame stays alive
in memory even after the outer function exits!



class="snippet" data-lang="js" data-hide="false" data-console="true"
data-babel="false">

class="snippet-code-js lang-js prettyprint-override">function say667()
{
// Local variable that ends up within closure
var num =
42;
var say = function() { console.log(num); }
num++;

return say;
}
var sayNumber = say667();
sayNumber(); //
logs
43





Example
4



All three global functions have a common
reference to the same closure because they are all declared within
a single call to
setupSomeGlobals().



class="snippet" data-lang="js" data-hide="false" data-console="true"
data-babel="false">

class="snippet-code-js lang-js prettyprint-override">var gLogNumber,
gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local
variable that ends up within closure
var num = 42;
// Store some
references to functions as global variables
gLogNumber = function() {
console.log(num); }
gIncreaseNumber = function() { num++; }

gSetNumber = function(x) { num = x;
}
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber();
// 43
gSetNumber(5);
gLogNumber(); // 5

var
oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); //
42

oldLog() //
5





The
three functions have shared access to the same closure — the local variables of
setupSomeGlobals() when the three functions were
defined.



Note that in the above example, if you
call setupSomeGlobals() again, then a new closure
(stack-frame!) is created. The old gLogNumber,
gIncreaseNumber, gSetNumber variables
are overwritten with new functions that have the new closure. (In
JavaScript, whenever you declare a function inside another function, the inside
function(s) is/are recreated again each time the outside function
is called.)



Example
5



This example shows that the closure contains
any local variables that were declared inside the outer function before it exited. Note
that the variable alice is actually declared after the
anonymous function. The anonymous function is declared first and when that function is
called it can access the alice variable because
alice is in the same scope (JavaScript does href="https://stackoverflow.com/a/3725763/1269037">variable
hoisting).
Also sayAlice()() just directly
calls the function reference returned from sayAlice() — it is
exactly the same as what was done previously but without the temporary
variable.



data-hide="false" data-console="true" data-babel="false">
class="snippet-code">
function sayAlice() {
var say =
function() { console.log(alice); }
// Local variable that ends up within
closure
var alice = 'Hello Alice';
return
say;
}
sayAlice()();// logs "Hello
Alice"





Tricky:
note the say variable is also inside the closure and could be
accessed by any other function that might be declared within
sayAlice(), or it could be accessed recursively within the
inside function.



Example
6



This one is a real gotcha for many people, so
you need to understand it. Be very careful if you are defining a function within a loop:
the local variables from the closure may not act as you might first think.



You need to understand the "variable hoisting"
feature in Javascript in order to understand this
example.



data-hide="false" data-console="true" data-babel="false">
class="snippet-code">
function buildList(list) {
var result =
[];
for (var i = 0; i < list.length; i++) {
var item = 'item' +
i;
result.push( function() {console.log(item + ' ' + list[i])} );

}
return result;
}

function testList()
{
var fnlist = buildList([1,2,3]);
// Using j only to help prevent
confusion -- could use i.
for (var j = 0; j < fnlist.length; j++)
{
fnlist[j]();
}
}

testList()
//logs "item2 undefined" 3
times





The
line result.push( function() {console.log(item + ' ' +
list[i])}
adds a reference to an anonymous function three times to the
result array. If you are not so familiar with anonymous functions think of it
like:



pointer = function()
{console.log(item + ' ' +
list[i])};
result.push(pointer);


Note
that when you run the example, "item2 undefined" is logged
three times! This is because just like previous examples, there is only one closure for
the local variables for buildList (which are
result, i,
list and item). When the anonymous
functions are called on the line fnlist[j](); they all use the
same single closure, and they use the current value for i and
item within that one closure (where i
has a value of 3 because the loop had completed, and
item has a value of 'item2'). Note we
are indexing from 0 hence item has a value of
item2. And the i++ will increment i to
the value 3.



It may be
helpful to see what happens when a block-level declaration of the variable
item is used (via the let keyword)
instead of a function-scoped variable declaration via the var
keyword. If that change is made, then each anonymous function in the array
result has its own closure; when the example is run the output
is as follows:



item0
undefined
item1 undefined
item2
undefined


If the
variable i is also defined using let
instead of var, then the output
is:



item0 1
item1
2
item2
3


Example
7



In this final example, each call to the main
function creates a separate closure.



class="snippet" data-lang="js" data-hide="false" data-console="true"
data-babel="false">

class="snippet-code-js lang-js prettyprint-override">function
newClosure(someNum, someRef) {
// Local variables that end up within
closure
var num = someNum;
var anArray = [1,2,3];
var
ref = someRef;
return function(x) {
num += x;

anArray.push(num);
console.log('num: ' + num +
'; anArray: ' +
anArray.toString() +
'; ref.someVar: ' + ref.someVar + ';');

}
}
obj = {someVar: 4};
fn1 = newClosure(4,
obj);
fn2 = newClosure(5, obj); // attention here: new closure assigned to a
new variable!
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar:
4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar:
4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7;
ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar:
5;





Summary



If
everything seems completely unclear, then the best thing to do is to play with the
examples. Reading an explanation is much harder than understanding
examples.
My explanations of closures and stack-frames, etc. are not
technically correct — they are gross simplifications intended to help to understand.
Once the basic idea is grokked, you can pick up the details
later.



Final
points:




  • Whenever you use
    function inside another function, a closure is
    used.

  • Whenever you use eval()
    inside a function, a closure is used. The text you eval can
    reference local variables of the function, and within eval you
    can even create new local variables by using eval('var foo =
    …')

  • When you use new
    Function(…)
    (the href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function"
    rel="noreferrer">Function constructor) inside a function, it does not
    create a closure. (The new function cannot reference the local variables of the outer
    function.)

  • A closure in JavaScript is like keeping a copy
    of all the local variables, just as they were when a function
    exited.

  • It is probably best to think that a closure is
    always created just an entry to a function, and the local variables are added to that
    closure.

  • A new set of local variables is kept every time
    a function with a closure is called (given that the function contains a function
    declaration inside it, and a reference to that inside function is either returned or an
    external reference is kept for it in some way).

  • Two
    functions might look like they have the same source text, but have completely different
    behavior because of their 'hidden' closure. I don't think JavaScript code can actually
    find out if a function reference has a closure or not.

  • If
    you are trying to do any dynamic source code modifications (for example:
    myFunction =
    Function(myFunction.toString().replace(/Hello/,'Hola'));
    ), it won't work if
    myFunction is a closure (of course, you would never even think
    of doing source code string substitution at runtime,
    but...).

  • It is possible to get function declarations
    within function declarations within functions… and you can get closures at more than one
    level.

  • I think normally a closure is a term for both the
    function along with the variables that are captured. Note that I do not use that
    definition in this article!

  • I suspect that closures in
    JavaScript differ from those normally found in functional
    languages.



Links





Thanks



If
you have just learned closures (here or elsewhere!), then I am
interested in any feedback from you about any changes you might suggest that could make
this article clearer. Send an email to morrisjohns.com (morris_closure @). Please note
that I am not a guru on JavaScript — nor on
closures.



/>

Original post by Morris can be found in the href="http://web.archive.org/web/20080209105120/http:/blog.morrisjohns.com/javascript_closures_for_dummies"
rel="noreferrer">Internet Archive.



No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...