question about closures

S

Scott Sauyet

I don't quite understand why the following works but not its commented
out version:

http://pastebin.com/qTkevTYg

I'll attempt an explanation below, but first, you should realize that
on USENET, it's considered good form to post sample code in your
message and not just a link. Some people don't have access to the
linked site when they're reading. A link is fine for additional
details, but there should be a minimal example inline. To that end,
here's an abbreviated portion of the posted code:

This is not working as expected:

for (i in coords) {
// create latlng
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(i);
});
map.addOverlay(marker);
}

This is working as expected:

function createMarker(latlng, number) {
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(number);
});
return marker;
}

for (i in coords) {
// create latlng
map.addOverlay(createMarker(latlng, i));
}


You definitely made a step in the right direction when you used
"closures" in your title. Your issue is a common one with loop
variables and closures.

I won't try to describe closures in any detail. The first few links
in a Google search of "javascript closures" are usually quite good.
And the FAQ for this group contains Richard Cornford's excellent
reference page on closures [1], although it's not aimed at novices.

The main point of a closure is that an inner function allowed to
escape from an outer function has access to all the local variables,
parameters, and inner functions of the outer function, even after the
outer function has completed. They have access to the current state
of the local variables, not the state they had when the inner function
was created. So in your first example, when this line is eventually
called:

marker.openInfoWindowHtml(i);

it has access to the current state of the variable `i`, which would
presumably be the last element in the iteration over `coords`.

In your second example, the closure contains the local variables and
parameters of your call to `createMarker`. But that call was made
with a particular value for `i`, which was stored as `number` in the
closure. (The name is not important. The same would work if you
reused `i` instead of using `number`; it's that it is a parameter of
the function captured by the closure.)

A simple way to avoid these problems is not to use loop variables in
any inner functions that might escape the current function.

[1] FAQ closure article: <http://jibbering.com/faq/notes/closures/>
 
D

Denis McMahon

This is not working as expected:

for (i in coords) {
// create latlng
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(i);
});
map.addOverlay(marker);
}

This is working as expected:

function createMarker(latlng, number) {
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(number);
});
return marker;
}

for (i in coords) {
// create latlng
map.addOverlay(createMarker(latlng, i));
}

[snipped Scott's explanation]

Scott, would the following approach also solve the problem?

for (var i in coords) {
// create latlng
var marker = new GMarker(latlng);
var j = i; // copy of current i on each iteration of loop
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(j); // use the copied i
});
map.addOverlay(marker);
}

Rgds

Denis McMahon
 
G

Gregor Kofler

Am 2011-02-10 12:01, Denis McMahon meinte:
This is not working as expected:

for (i in coords) {
// create latlng
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(i);
});
map.addOverlay(marker);
}

This is working as expected:

function createMarker(latlng, number) {
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(number);
});
return marker;
}

for (i in coords) {
// create latlng
map.addOverlay(createMarker(latlng, i));
}

[snipped Scott's explanation]

Scott, would the following approach also solve the problem?

for (var i in coords) {
// create latlng
var marker = new GMarker(latlng);
var j = i; // copy of current i on each iteration of loop
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(j); // use the copied i
});
map.addOverlay(marker);
}

Why should this make a difference? The "closured" value of j would be
set to the latest value, too.

Gregor
 
D

Denis McMahon

Am 2011-02-10 12:01, Denis McMahon meinte:
This is not working as expected:

for (i in coords) {
// create latlng
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(i);
});
map.addOverlay(marker);
}

This is working as expected:

function createMarker(latlng, number) {
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(number);
});
return marker;
}

for (i in coords) {
// create latlng
map.addOverlay(createMarker(latlng, i));
}

[snipped Scott's explanation]

Scott, would the following approach also solve the problem?

for (var i in coords) {
// create latlng
var marker = new GMarker(latlng);
var j = i; // copy of current i on each iteration of loop
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(j); // use the copied i
});
map.addOverlay(marker);
}

Why should this make a difference? The "closured" value of j would be
set to the latest value, too.

Because j is instanced on each run through the loop (var j), unlike i
which is instanced as the loop control variable.

Therefore, each j (as I understand it) is a different j, having a scope
of the current iteration of the loop, and in each case having a copy of
the specific value of i at that iteration of the loop.

That's my interpretation, which is why I'm asking the question.

Rgds

Denis McMahon
 
D

Denis McMahon

Javascript doesn't create new scopes just because
you use 'var' inside some curly braces: only functions create new
variable scopes.

Ah. I'm surprised this has never bitten me on the arse. Hard! But
surprisingly it hasn't, despite the fact that I've managed to remain
completely unaware of it so far.

Rgds

Denis McMahon
 
G

Gregor Kofler

Am 2011-02-10 13:56, Denis McMahon meinte:
Am 2011-02-10 12:01, Denis McMahon meinte:
On 10/02/11 02:30, Scott Sauyet wrote:

This is not working as expected:

for (i in coords) {
// create latlng
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(i);
});
map.addOverlay(marker);
}

This is working as expected:

function createMarker(latlng, number) {
var marker = new GMarker(latlng);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(number);
});
return marker;
}

for (i in coords) {
// create latlng
map.addOverlay(createMarker(latlng, i));
}

[snipped Scott's explanation]

Scott, would the following approach also solve the problem?

for (var i in coords) {
// create latlng
var marker = new GMarker(latlng);
var j = i; // copy of current i on each iteration of loop
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(j); // use the copied i
});
map.addOverlay(marker);
}

Why should this make a difference? The "closured" value of j would be
set to the latest value, too.

Because j is instanced on each run through the loop (var j), unlike i
which is instanced as the loop control variable.

Therefore, each j (as I understand it) is a different j, having a scope
of the current iteration of the loop, and in each case having a copy of
the specific value of i at that iteration of the loop.

In JS only functions generate new scopes, repeated var declarations
within a scope are discarded. j always has the specific value of i
during each iteration (naturally), but when the eent listener gets
invoked it will retrieve the last value of j.

Gregor
 
T

Thomas 'PointedEars' Lahn

Denis said:
Because j is instanced on each run through the loop (var j),

No, it is not. There is no block scoping with the `var' statement (only
with the `let' statement, in Mozilla.org JavaScript 1.7+), and variable
instantiation (in ES5: declaration binding instantiation) takes place before
all assignments.
unlike i which is instanced as the loop control variable.

There is no semantical difference. The above is bytecode by bytecode
equivalent to:

var i;
var marker;
var j;

for (i in coords) {
marker = new GMarker(latlng);
j = i;
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(j);
});
map.addOverlay(marker);
}

This can be confirmed using any debugger that allows to inspect the current
scope.
Therefore, each j (as I understand it) is a different j, having a scope
of the current iteration of the loop, and in each case having a copy of
the specific value of i at that iteration of the loop.

That's my interpretation, which is why I'm asking the question.

So you can see that using the correct interpretation (as specified in the
ECMAScript Language Specification, Edition 3/5 Final, section 10, and widely
implemented so) instead, it does not make a difference.


HTH

PointedEars
 
T

Thomas 'PointedEars' Lahn

Denis said:
Ah. I'm surprised this has never bitten me on the arse. Hard! But
surprisingly it hasn't, despite the fact that I've managed to remain
completely unaware of it so far.

Duncan's statement is right or wrong, depending how you look at it, i.e. how
you define "Javascript".

Mozilla.org JavaScript, version 1.7 and newer, does allow new "variable
scopes" (the term itself is ill-advised) to be created with `let'
statements, expressions, or definitions.

This shows once again that using a single name resembling the original
JavaScript[tm] name in order to refer to all (relevant) ECMAScript
implementations is ill-advised.


PointedEars
 
J

John G Harris

This shows once again that using a single name resembling the original
JavaScript[tm] name in order to refer to all (relevant) ECMAScript
implementations is ill-advised.

And not offering a suggestion for a five-syllable-or-less alternative is
even more ill-advised.

John
 
S

Scott Sauyet

Duncan said:
Denis said:
[snipped Scott's explanation]
Scott, would the following approach also solve the problem?
[ ... snipped suggestion for adding a new `var` statement ... ]

No, Scott may have misled you by talking about the loop variable
specifically: the code in the nested function will always see the
current value of any variable that it accesses from its enclosing scope:
in this case both 'marker' and 'j' will have changed by the time the
event handler is called.

Yes, I missed that `marker` was used internally as well, seeing it
only in the `addListener` function, where it was appropriate.
Although loop variables are the most commonly-found issues, as Duncan
said, any variables in the closure are available only with their
current value at the time the inner function is executed.

Calling another function in order to create a new context is the most
common solution, and one which the OP seems to have already found.

-- Scott
 
T

Thomas 'PointedEars' Lahn

John said:
Thomas said:
This shows once again that using a single name resembling the original
JavaScript[tm] name in order to refer to all (relevant) ECMAScript
implementations is ill-advised.

And not offering a suggestion for a five-syllable-or-less alternative is
even more ill-advised.

An answer that can be expected from a simple mind.


PointedEars
 
J

John G Harris

John said:
Thomas said:
This shows once again that using a single name resembling the original
JavaScript[tm] name in order to refer to all (relevant) ECMAScript
implementations is ill-advised.

And not offering a suggestion for a five-syllable-or-less alternative is
even more ill-advised.

An answer that can be expected from a simple mind.

That from the man who objects to anyone saying that C uses semicolons.

John
 
T

Thomas 'PointedEars' Lahn

John said:
Thomas said:
John said:
Thomas 'PointedEars' Lahn wrote:
This shows once again that using a single name resembling the original
JavaScript[tm] name in order to refer to all (relevant) ECMAScript
implementations is ill-advised.
And not offering a suggestion for a five-syllable-or-less alternative is
even more ill-advised.
An answer that can be expected from a simple mind.

That from the man who objects to anyone saying that C uses semicolons.

Rubbish.


PointedEars
 
J

John G Harris

John said:
Thomas said:
John G Harris wrote:
Thomas 'PointedEars' Lahn wrote:
This shows once again that using a single name resembling the original
JavaScript[tm] name in order to refer to all (relevant) ECMAScript
implementations is ill-advised.
And not offering a suggestion for a five-syllable-or-less alternative is
even more ill-advised.
An answer that can be expected from a simple mind.

That from the man who objects to anyone saying that C uses semicolons.

Rubbish.

<almost quote>
This shows once again that using a single name, C, in order to refer to
all (relevant) desk-top computer, mini-computer, super-computer,
embedded computer implementations, and K & R, first and second ISO
standards is ill-advised.
</almost quote>

You need to be consistent in your strictures.

John
 

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

Staff online

Members online

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,054
Latest member
LucyCarper

Latest Threads

Top