JavaScript, Higher Order Functions, Closures, how come?

Discussion in 'Javascript' started by Emre Sevinc, Feb 14, 2006.

  1. Emre Sevinc

    Emre Sevinc Guest

    Take a look at the following snippet:

    <html>
    <head>
    <script>
    function add(elementId) {
    var container = document.getElementById(elementId);
    for (var i = 0; i < 10; i++) {
    var elt = document.createElement('div');
    elt.innerHTML = "" + i;

    // Beware!
    elt.onclick = function () {window.alert("" + i);};
    container.appendChild(elt);
    }
    }
    </script>

    </head>

    <body>
    <div id=myDiv onClick=add('myDiv')>
    Here's my DIV
    </div>
    </body>
    </html>

    Now, when you click on the DIV element it produces and adds 10
    DIV elements. That's okay but when you click on the child DIVs,
    alert gives you 10. Not that you click on the first one and
    you get 0 and the next one and 1, etc.

    Why does that anonymous function always show the value of 10?

    I'm very surprised at this output!

    Any ideas, explanations, tips?

    --
    Emre Sevinc

    eMBA Software Developer Actively engaged in:
    http:www.bilgi.edu.tr http://ileriseviye.org
    http://www.bilgi.edu.tr http://fazlamesai.net
    Cognitive Science Student http://cazci.com
    http://www.cogsci.boun.edu.tr
     
    Emre Sevinc, Feb 14, 2006
    #1
    1. Advertising

  2. Emre Sevinc

    Emre Sevinc Guest

    >>>>> "DB" == Duncan Booth <> writes:

    DB> Emre Sevinc wrote:
    >> <html> <head> <script> function add(elementId) { var container
    >> = document.getElementById(elementId); for (var i = 0; i < 10;
    >>[...]
    >> Why does that anonymous function always show the value of 10?
    >>

    DB> The alert shows you the value of 'i' at the time when alert is
    DB> called. i.e. by the time you click on the div the loop has
    DB> finished running and 'i' is 10.

    That's what still confuses me. I'm generating a function on-the-fly
    and assigning it to a new DIV element's onClick attribute and
    this is done before the loop ends. Right? If that is right, I mean
    that generated functions must be taking values 0, 1, ... 9. I think
    this is the crucial point and which confuses me. What kind of an order
    of execution is this?

    DB> When you access a variable from an outer scope inside a
    DB> function it always acts on the current value of the
    DB> variable. You seem to have expected it to make a copy of the
    DB> variable's value when the function was created, but it won't
    DB> do that.

    Yes, you're right. I expected it to build a function with the
    values I have provided = 0, 1, ..., 9 (and I don't understand
    why it didn't).

    DB> If you want to use the value at the time the function is
    DB> created you have to save that value in another variable. One
    DB> way to do this is to create the function inside another
    DB> factory function:

    DB> function new_alert(value) { return function ()
    DB> {window.alert(value);}; };

    DB> ... elt.onclick = new_alert(i);

    DB> This way the parameter of the factory function remains
    DB> unchanged even though i is incremented so you see the value
    DB> you expected.

    Yes, it works now, but I'm still somehow baffled at this situation
    (my mind is swinging between Lisp and JavaScript! :))

    Thank you very much for the explanations.


    --
    Emre Sevinc

    eMBA Software Developer Actively engaged in:
    http:www.bilgi.edu.tr http://ileriseviye.org
    http://www.bilgi.edu.tr http://fazlamesai.net
    Cognitive Science Student http://cazci.com
    http://www.cogsci.boun.edu.tr
     
    Emre Sevinc, Feb 14, 2006
    #2
    1. Advertising

  3. Emre Sevinc wrote:


    > function add(elementId) {
    > var container = document.getElementById(elementId);
    > for (var i = 0; i < 10; i++) {
    > var elt = document.createElement('div');
    > elt.innerHTML = "" + i;
    >
    > // Beware!
    > elt.onclick = function () {window.alert("" + i);};
    > container.appendChild(elt);
    > }
    > }


    > Now, when you click on the DIV element it produces and adds 10
    > DIV elements. That's okay but when you click on the child DIVs,
    > alert gives you 10. Not that you click on the first one and
    > you get 0 and the next one and 1, etc.
    >
    > Why does that anonymous function always show the value of 10?


    Well your subject names the reason, closures. (There are no higher order
    functions (functions taking functions as arguments) however, not sure
    why your subject names them too).

    As for the closure, those anynymous functions you defined as the onclick
    event handler are inner functions of the function add in which the
    variable i is declared. When add is called and executed, that variable i
    is incremented from 0 to 10 and the last value is what is available in
    the closure to those inner functions as they are executed after the add
    call is finished. If you executed an onclick handler during the add call
    then of course the current value of i is what is accessed e.g.

    function add() {
    var container = document.body;
    for (var i = 0; i < 10; i++) {
    var elt = document.createElement('div');
    elt.appendChild(document.createTextNode(i));
    elt.onclick = function () {window.alert("" + i);};
    container.appendChild(elt);
    if (i == 5) {
    elt.onclick();
    }
    }
    }
    add();

    And i can be changed by those inner functions during the execution of
    add and after add has been finished:

    function add() {
    var container = document.body;
    for (var i = 0; i < 10; i++) {
    var elt = document.createElement('div');
    elt.appendChild(document.createTextNode(i));
    elt.onclick = function () { alert(++i); };
    container.appendChild(elt);
    if (i == 5) {
    elt.onclick();
    }
    }
    }
    add();




    Check the FAQ notes on closures:
    <http://www.jibbering.com/faq/faq_notes/closures.html>

    If you want to have an onclick handler that uses the i value that i has
    during the creation of the event handler then a function constructed
    with new Function e.g.
    elt.onclick = new Function("evt", "alert(" + i + ");");
    is one way.
    --

    Martin Honnen
    http://JavaScript.FAQTs.com/
     
    Martin Honnen, Feb 14, 2006
    #3
  4. On 14/02/2006 13:41, Emre Sevinc wrote:

    [snip]

    > <script>


    The type attribute is required:

    <script type="text/javascript">

    > function add(elementId) {
    > var container = document.getElementById(elementId);
    > for (var i = 0; i < 10; i++) {
    > var elt = document.createElement('div');
    > elt.innerHTML = "" + i;


    Though I personally prefer to use the String function to perform string
    conversions, it is not necessary here; the conversion is implicit.

    > // Beware!


    Indeed...

    > elt.onclick = function () {window.alert("" + i);};


    ....this closure will form a circular reference involving DOM nodes
    (container and elt). In IE, this will lead to a memory leak[1]. The loop
    can be broken by assigning null before the add function returns.

    [snip]

    > <div id=myDiv onClick=add('myDiv')>


    That onclick attribute value must be quoted:

    <div id=myDiv onclick="add('myDiv');">

    However, I would advise that you quote /all/ attribute values.

    [snip]

    > Why does that anonymous function always show the value of 10?


    The values of variables in the scope chain of a function are not frozen.
    They can be changed during execution. Each time the function expression
    is evaluated, a new execution context is created, but each of these
    contexts share the four local variables present in the scope of the add
    function: elementId, container, i, and elt. As the loop continues
    modifies the latter two, each created function object will see these
    modifications.

    [snip]

    > Any ideas, explanations, tips?


    You could either use the Function constructor, or create a separate
    function to perform the listener assignment.

    function add(id) {
    var container = document.getElementById(id);

    for(var i = 0; i < 10; ++i) {
    var element = document.createElement('div');

    element.appendChild(document.createTextNode(i));
    element.onclick = new Function('alert(\'' + i + '\');');

    container.appendChild(element);
    }
    container = element
    = null;
    }

    Or replacing the onclick assignment with:

    element.onclick = createListener(i);

    removing the null assignment (it's not necessary, here), and adding:

    function createListener(value) {
    return function() {
    alert(value);
    };
    }


    For clarity, feature detection was omitted from the code above. It
    should be included in production code.

    Hope that helps,
    Mike


    [1] <http://www.jibbering.com/faq/faq_notes/closures.html#clMem>

    --
    Michael Winter
    Prefix subject with [News] before replying by e-mail.
     
    Michael Winter, Feb 14, 2006
    #4
  5. Hello Emre,


    Emre Sevinc <> writes:
    >
    > Yes, it works now, but I'm still somehow baffled at this situation
    > [...]
    > (my mind is swinging between Lisp and JavaScript! :))


    It's absolutely the same in Lisp; check this out:

    -------------------------------
    aundro@paddy:~$ sbcl
    This is SBCL 0.9.6, an implementation of ANSI Common Lisp.
    More information about SBCL is available at <http://www.sbcl.org/>.

    SBCL is free software, provided as is, with absolutely no warranty.
    It is mostly in the public domain; some portions are provided under
    BSD-style licenses. See the CREDITS and COPYING files in the
    distribution for more information.
    * (defvar *foo* nil)

    *FOO*
    * (dotimes (i 10)
    (push (lambda () (format t "~s~%" i)) *foo*))

    NIL
    * *foo*

    (#<CLOSURE (LAMBDA #) {906C4DD}> #<CLOSURE (LAMBDA #) {906C4C5}>
    #<CLOSURE (LAMBDA #) {906C4AD}> #<CLOSURE (LAMBDA #) {906C495}>
    #<CLOSURE (LAMBDA #) {906C47D}> #<CLOSURE (LAMBDA #) {906C465}>
    #<CLOSURE (LAMBDA #) {906C44D}> #<CLOSURE (LAMBDA #) {906C435}>
    #<CLOSURE (LAMBDA #) {906C41D}> #<CLOSURE (LAMBDA #) {906C405}>)
    * (funcall (nth 5 *foo*))
    10
    NIL
    * (funcall (nth 1 *foo*))
    10
    NIL
    *
    -------------------------------

    Best regards,

    Arnaud
     
    Arnaud Diederen, Feb 14, 2006
    #5
  6. Michael Winter wrote:

    > On 14/02/2006 13:41, Emre Sevinc wrote:
    >> function add(elementId) {
    >> var container = document.getElementById(elementId);
    >> for (var i = 0; i < 10; i++) {
    >> var elt = document.createElement('div');
    >> elt.innerHTML = "" + i;

    > [...]
    >> // Beware!

    >
    > Indeed...
    >
    >> elt.onclick = function () {window.alert("" + i);};

    >
    > ...this closure will form a circular reference involving DOM nodes
    > (container and elt). In IE, this will lead to a memory leak[1]. The loop
    > can be broken by assigning null before the add function returns.


    Could you please elaborate what the circular reference would be here?
    Because I do not see it (yet).


    PointedEars
     
    Thomas 'PointedEars' Lahn, Feb 14, 2006
    #6
  7. On 14/02/2006 17:51, Thomas 'PointedEars' Lahn wrote:

    >> On 14/02/2006 13:41, Emre Sevinc wrote:
    >>
    >>> function add(elementId) {
    >>> var container = document.getElementById(elementId);
    >>> for (var i = 0; i < 10; i++) {
    >>> var elt = document.createElement('div');
    >>> elt.innerHTML = "" + i;


    [snip]

    >>> elt.onclick = function () {window.alert("" + i);};


    [snip]

    > Could you please elaborate what the circular reference would be here?
    > Because I do not see it (yet).


    elt -> elt.onclick
    -> <Function object>
    -> <Function object>.[[Scope]]
    -> Variable object of add function
    -> <Variable object>.elt

    This is the same circular reference described in the FAQ notes.

    Mike

    --
    Michael Winter
    Prefix subject with [News] before replying by e-mail.
     
    Michael Winter, Feb 14, 2006
    #7
  8. Michael Winter wrote:

    > On 14/02/2006 17:51, Thomas 'PointedEars' Lahn wrote:
    >>> On 14/02/2006 13:41, Emre Sevinc wrote:
    >>>> function add(elementId) {
    >>>> var container = document.getElementById(elementId);
    >>>> for (var i = 0; i < 10; i++) {
    >>>> var elt = document.createElement('div');
    >>>> elt.innerHTML = "" + i;

    > [snip]
    >>>> elt.onclick = function () {window.alert("" + i);};

    > [snip]
    >> Could you please elaborate what the circular reference would be here?
    >> Because I do not see it (yet).

    >
    > elt -> elt.onclick
    > -> <Function object>
    > -> <Function object>.[[Scope]]
    > -> Variable object of add function
    > -> <Variable object>.elt
    >
    > This is the same circular reference described in the FAQ notes.


    Is it? So far I see no reference to the Variable Object of add's execution
    context from the scope of the event listener. `i' is a local variable in
    add's context storing a primitive (number) value and the closure defines
    the event listener statically without any object reference to `add'.


    PointedEars
     
    Thomas 'PointedEars' Lahn, Feb 14, 2006
    #8
  9. Thomas 'PointedEars' Lahn wrote:
    > Michael Winter wrote:
    >> On 14/02/2006 13:41, Emre Sevinc wrote:
    >>> function add(elementId) {
    >>> var container = document.getElementById(elementId);
    >>> for (var i = 0; i < 10; i++) {
    >>> var elt = document.createElement('div');
    >>> elt.innerHTML = "" + i;

    >> [...]
    >>> // Beware!

    >>
    >> Indeed...
    >>
    >>> elt.onclick = function () {window.alert("" + i);};

    >>
    >> ...this closure will form a circular reference involving DOM
    >> nodes (container and elt). In IE, this will lead to a memory
    >> leak[1]. The loop can be broken by assigning null before the
    >> add function returns.

    >
    > Could you please elaborate what the circular reference would
    > be here? Because I do not see it (yet).


    The various DIVs created are all referred to by the element that they
    are appended to, as its first and last child and through its childNodes
    collection, and Each DIV refers to its neighbours as its previous and
    next sibling. Each of those DIV elements has an onclick property that
    refers to a function object. Each function object has an internal
    [[Scope]] property that refers to its scope chain, each scope chain
    includes (by reference) the Activation/Variable object of the single
    execution context in which all the function objects were created, and
    that Activation/Variable object has a - container - property referring
    to the element to which the DIVs were appended and an - elt - property
    referring to the last DIV created. That is quite a lot of circular
    chains of reference including DOM nodes.

    The apparently easiest points to break the chains are the two local
    variables of the outer function, which could be assigned null before the
    function returned. Unfortunately as the - container - element must have
    an ID and IE makes IDed elements available as properties of the global
    object, and the global object is at the end of all scope chains, nulling
    the references won't actually solve the problem. The only real solution
    to the memory leak here is to null the onclick properties of the DIV
    elements (as the page unloads, if not sooner).

    Richard.
     
    Richard Cornford, Feb 14, 2006
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jonathan Bartlett

    Higher-Order Programming in C

    Jonathan Bartlett, Mar 31, 2005, in forum: C Programming
    Replies:
    4
    Views:
    312
    Jonathan Bartlett
    Apr 4, 2005
  2. Mark Fink
    Replies:
    3
    Views:
    303
    Arnaud Delobelle
    Dec 22, 2010
  3. Nickolay Kolev

    Higher Order Functions

    Nickolay Kolev, Jul 31, 2005, in forum: Ruby
    Replies:
    5
    Views:
    164
    Nickolay Kolev
    Aug 8, 2005
  4. Victor \Zverok\ Shepelev

    Higher-order messaging in Ruby

    Victor \Zverok\ Shepelev, Oct 11, 2006, in forum: Ruby
    Replies:
    0
    Views:
    132
    Victor \Zverok\ Shepelev
    Oct 11, 2006
  5. java
    Replies:
    0
    Views:
    95
Loading...

Share This Page