JavaScript, Higher Order Functions, Closures, how come?

E

Emre Sevinc

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
 
E

Emre Sevinc

DB> Emre Sevinc said:
<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
 
M

Martin Honnen

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.
 
M

Michael Winter

On 14/02/2006 13:41, Emre Sevinc wrote:

[snip]

The type attribute is required:

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>
 
A

Arnaud Diederen

Hello Emre,


Emre Sevinc said:
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
*
 
T

Thomas 'PointedEars' Lahn

Michael said:
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
 
M

Michael Winter

[snip]
[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
 
T

Thomas 'PointedEars' Lahn

Michael said:
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
 
R

Richard Cornford

Thomas said:
Michael said:
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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top