473,394 Members | 1,893 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,394 software developers and data experts.

JavaScript, Higher Order Functions, Closures, how come?


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
Feb 14 '06 #1
10 2047
Emre Sevinc wrote:
<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?

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

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

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

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

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

This way the parameter of the factory function remains unchanged even
though i is incremented so you see the value you expected.
Feb 14 '06 #2
>>>>> "DB" == Duncan Booth <du**********@invalid.invalid> 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
Feb 14 '06 #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/
Feb 14 '06 #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.
Feb 14 '06 #5
Emre Sevinc wrote:
>> "DB" == Duncan Booth <du**********@invalid.invalid> 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?


Its a completely logical order of execution. Functions execute when they
are called and not before.

You are generating a function on-the-fly and assigning the function to an
onclick attribute, but you aren't calling the function until the div is
clicked.

It doesn't matter where in the block the function is defined: the code
inside the function is executed when it is called, NOT when it is defined.

Your function:

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);
}
}

could equally well be written with the function outside the loop:

function add(elementId) {
var callme = function() { window.alert("" + i);};

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

// Beware!
elt.onclick = callme;
container.appendChild(elt);
}
}
If you still aren't clear then consider:

function test() {
var container = [];
for (var i = 0; i < 10; i++) {
container.push(function() { alert(i); });
container[0]();
}
container[9]();
i = 42;
container[9]();
}
test();

Its the same thing as you were doing: save the function then call it later.
Every time you call the function it shows you the current value of 'i'.
Outside the loop it shows you the final value of i, but you can reassign it
to whatever you want.
Feb 14 '06 #6

Hello Emre,
Emre Sevinc <em***@bilgi.edu.tr> 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
Feb 14 '06 #7
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
Feb 14 '06 #8
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.
Feb 14 '06 #9
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
Feb 14 '06 #10
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.
Feb 14 '06 #11

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

699
by: mike420 | last post by:
I think everyone who used Python will agree that its syntax is the best thing going for it. It is very readable and easy for everyone to learn. But, Python does not a have very good macro...
3
by: Anonymous | last post by:
Hello folks -- I would like a JavaScript tutorial for programmers. By this I mean that I'd like to know the language "fundamentals" /first/ and then learn about the "web stuff". This is because...
4
by: Derek | last post by:
Hi, I've built a rather large CGI that dumps a lot of data and a fairly complex javascript app out to the client's browser. Granted this may be poor style according to someone web design...
53
by: Cardman | last post by:
Greetings, I am trying to solve a problem that has been inflicting my self created Order Forms for a long time, where the problem is that as I cannot reproduce this error myself, then it is...
136
by: Matt Kruse | last post by:
http://www.JavascriptToolbox.com/bestpractices/ I started writing this up as a guide for some people who were looking for general tips on how to do things the 'right way' with Javascript. Their...
2
by: Jake Barnes | last post by:
Using javascript closures to create singletons to ensure the survival of a reference to an HTML block when removeChild() may remove the last reference to the block and thus destory the block is...
10
by: John Passaniti | last post by:
(Note: This is not the same message I posted a week or so ago. The problem that prevented my previous attempt to work was a silly error in the template system I was using. This is a problem...
6
by: Xcriber51 | last post by:
Hi I've come here after googling for this a day or two and not being able to spot what I'm looking for. I need a JavaScript IDE -- no frills, nothing special -- that offers me the ability to...
4
by: MartinRinehart | last post by:
I've written a short article explaining closures in JavaScript. It's at: http://www.martinrinehart.com/articles/javascript-closures.html I think I've understood. I look forward to your...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.