Lexical scope and looping

E

enaeher

I would expect this code:

globalFnArray = [];
for (var i = 0; i < 5; i++) { globalFnArray.push (function () { alert
(i) }); }
for (var j = 0; j < 5; j++) { globalFnArray[j](); }

to alert 0, 1, 2, 3, and 4.

Instead, I get 5, 5, 5, 5, and 5 in Firefox, indicating that all of
the functions in globalFnArray are closing over the final value of i
(after the for loop's conditional (i < 5) has failed and the loop has
exited), rather than the value to which i was bound during the
iteration of the loop when the function was defined. This does not
seem consistent with lexical scope as I understand it. Is the for loop
a special case? I'd appreciate any suggestions as to what I'm missing.

Thanks,
--Eli
 
L

-Lost

I would expect this code:

globalFnArray = [];
for (var i = 0; i < 5; i++) { globalFnArray.push (function () { alert
(i) }); }
for (var j = 0; j < 5; j++) { globalFnArray[j](); }

to alert 0, 1, 2, 3, and 4.

Instead, I get 5, 5, 5, 5, and 5 in Firefox, indicating that all of
the functions in globalFnArray are closing over the final value of i
(after the for loop's conditional (i < 5) has failed and the loop has
exited), rather than the value to which i was bound during the
iteration of the loop when the function was defined. This does not
seem consistent with lexical scope as I understand it. Is the for loop
a special case? I'd appreciate any suggestions as to what I'm missing.

I am not entirely sure how this relates to scope.

The problem is that you are storing functions with a variable inside them. Let me just
illustrate. What is being stored in each call to push() is:

function () {
alert(i);
}

So, when you finally call the function, i has already reached 5, and of course stays
there.

So each call iteration through globalFnArray will result in alert('5'); The only way I
see of you achieving what you want is:

globalFnArray = [];
for (var i = 0; i < 5; i++)
{
globalFnArray.push(function()
{
alert(i);
});
}
for (var i = 0, j = 0; j < 5; i++, j++)
{
globalFnArray[j](i);
}

*OR* change just these lines:

globalFnArray.push(function() to globalFnArray.push(function(i)

Then you could globalFnArray[j](i) anywhere you wanted, as long as it is fed a variable of
i.

-Lost
 
L

-Lost

-Lost said:
I would expect this code:

globalFnArray = [];
for (var i = 0; i < 5; i++) { globalFnArray.push (function () { alert
(i) }); }
for (var j = 0; j < 5; j++) { globalFnArray[j](); }

to alert 0, 1, 2, 3, and 4.

Instead, I get 5, 5, 5, 5, and 5 in Firefox, indicating that all of
the functions in globalFnArray are closing over the final value of i
(after the for loop's conditional (i < 5) has failed and the loop has
exited), rather than the value to which i was bound during the
iteration of the loop when the function was defined. This does not
seem consistent with lexical scope as I understand it. Is the for loop
a special case? I'd appreciate any suggestions as to what I'm missing.

I am not entirely sure how this relates to scope.

The problem is that you are storing functions with a variable inside them. Let me just
illustrate. What is being stored in each call to push() is:

function () {
alert(i);
}

So, when you finally call the function, i has already reached 5, and of course stays
there.

So each call iteration through globalFnArray will result in alert('5'); The only way I
see of you achieving what you want is:

globalFnArray = [];
for (var i = 0; i < 5; i++)
{
globalFnArray.push(function()
{
alert(i);
});
}
for (var i = 0, j = 0; j < 5; i++, j++)
{
globalFnArray[j](i);

Oops! Here I meant:

globalFnArray[j]();
}

*OR* change just these lines:

globalFnArray.push(function() to globalFnArray.push(function(i)

Then you could globalFnArray[j](i) anywhere you wanted, as long as it is fed a variable
of i.
 
E

enaeher

The problem is that you are storing functions with a variable inside them.

Yes, I am trying to make a closure.

However, after further experimentation I've realized that I did
misunderstand lexical scope -- I thought that functions could close
over a variable value at the point in a lexical block (such as a
function definition) at which they were defined, but in fact they can
only close over the last value in that lexical block, irrespective of
where in the block the function is defined.

So I think the way to do what I was trying to do is:

globalFnArray = [];

for (var i = 0; i < 5; i++) {
globalFnArray.push (
function () {
var x = i;
return function () { alert (x) }
}()
);
}

for (var j = 0; j < 5; j++) { globalFnArray[j](); }

If there is a nicer way to do this which doesn't require using a
throwaway variable, I'd love to know.

Thanks,

--Eli
 
K

Kevin

If there is a nicer way to do this which doesn't require using a
throwaway variable, I'd love to know.

You could use a constructor to create the function type for what
you're trying to do:

globalFnArray = [];

function alertfunc(x) {
return function() { alert(x); };
}

for (var i = 0; i < 5; i++) {
globalFnArray.push ( new alertfunc(i) );
}

for (var j = 0; j < 5; j++) { globalFnArray[j](); }


Still not the prettiest thing, but maybe more like what you're looking
for?

- Kevin
 
R

RobG

Yes, I am trying to make a closure.

You did, but to a global variable (i). In this case, you can set i to
whatever value you want later, all the functions will show the same
value.
However, after further experimentation I've realized that I did
misunderstand lexical scope -- I thought that functions could close
over a variable value at the point in a lexical block (such as a
function definition) at which they were defined, but in fact they can
only close over the last value in that lexical block, irrespective of
where in the block the function is defined.

"close over"? All the function objects that you put in the array have
a reference back to the same i variable, which is global. Consider:

var gArray = [];

function foo(){

// This i is local to foo
for (var i=0; i<5; i++){

// Each fn has a closure back to foo's i
gArray.push( function(){alert(i);} );
}
}

foo();

// This i is global
for (var i=0, len=gArray.length; i<len; i++){
gArray();
}

So I think the way to do what I was trying to do is:

globalFnArray = [];

for (var i = 0; i < 5; i++) {
globalFnArray.push (
function () {
var x = i;
return function () { alert (x) }
}()
);

}

for (var j = 0; j < 5; j++) { globalFnArray[j](); }

If there is a nicer way to do this which doesn't require using a
throwaway variable, I'd love to know.

You aren't throwing away the variable at all, you are putting it into
the new function object with the value of whatever i was when you
create the function.

Anyhow, you have to break the closure somehow. You can also use a new
Function, however I think the above way is better as its much more
efficient - new Function is similar to using eval. But for the
record:

for (var i=0; i<5; i++){
gArray.push( new Function('alert(' + i + ');') );
}
 
R

ron.h.hall

The problem is that you are storing functions with a variable inside them.

Yes, I am trying to make a closure.

However, after further experimentation I've realized that I did
misunderstand lexical scope -- I thought that functions could close
over a variable value at the point in a lexical block (such as a
function definition) at which they were defined, but in fact they can
only close over the last value in that lexical block, irrespective of
where in the block the function is defined.

So I think the way to do what I was trying to do is:

globalFnArray = [];

for (var i = 0; i < 5; i++) {
globalFnArray.push (
function () {
var x = i;
return function () { alert (x) }
}()
);

}

for (var j = 0; j < 5; j++) { globalFnArray[j](); }

If there is a nicer way to do this which doesn't require using a
throwaway variable, I'd love to know.

for (var i = 0; i < 5; i++) {
globalFnArray.push (
function (myPosn) {
return function () { alert (myPosn); }
}(i)
);
}
 

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

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top