Skip to content Skip to sidebar Skip to footer

Javascript Puzzle: Scope

So I am trying to figure out this puzzle: function fun1(){ var result = []; for (var i = 0; i < 5; i++){ result.push( function() {return i} ); } return r

Solution 1:

This happens because your function (closure) maintains a

  • reference to i

rather than

  • a snapshot of i as it existed during each particular iteration.

This means that your function, when executed, will know the current value of i at that instant. Because the loop will already have finished at that point, that will be the last value of i set by the loop. So, how do we get the snapshot value instead of the reference? Luckily, numeric parameters are passed by value... so, you can avoid your issue by passing i to a second function...(fiddle):

functionfun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(x){
            result.push(function() { 
                return x; // x now has i's snapshotted value
            });
        })(i); // passes i by value since it's numeric
    }
    return result;
}

alert(fun1()[0]())

[Edit] Let's take the loop out of the equation (fiddle):

var i = 1;
varfn = function() { // this closure maintains a reference to i 
    alert(i); 
};
i = 10; // such that when we change i's value herefn(); // it's reflected by the function call: alerting 10

So, to "fix" this (fiddle):

var i = 1;
varfn = (function(x) { // this is a self-executing anonymous function
    return function() { // this closure maintains a reference to x
        alert(x); 
    };
})(i); // we're passing i by value such that x will retain that value
i = 10; // when we change i here, it has no affect on xfn(); // this will alert 1

Solution 2:

Let's break down what happens step by step:

  • We declare a function fun1() that returns an array result.

  • The for loop iterates 5 times, with each iteration incrementing i.

  • Notice that the anonymous function returning i is not invoked throughout the execution.

  • The for loop ends with the value of i being 5.

  • Invoking fun1()[0] returns an array result[] which has a function that stores the reference to i, not the value of i.

  • Invoking the anonymous function then follows that reference and returns the value of i, which is 5.

Solution 3:

You should read a little about Javascript closures, look it up.

The variable i from the fun1 function is accessible from the function in the loop, but it's the same variable, not a clone.

Solution 4:

The value of "i" in the inner function will be the value of "i" in the outer function when it returns. So, all 5 elements in the array will return "5" when called.

Solution 5:

What a question!

The problem is, in javascript, as in java, integer values are passed by value, not by reference. Which is what everyone else here is telling you. But i'm assuming you don't know what this means. It is pretty standard across languages, so let's see if i can explain!

NOTE: this is a crippled explanation. I don't really know how javascript is passing the values, and fun terms like "stack" and "heap" and "pass by reference value" (which i think is technically correct?) can pop up in more academic answers. Hopefully i can skip all that, and assume that

  1. there is one place where the computer stores memory for programs (there isn't)
  2. it is stored in hex notation on RAM (maybe it does? i don't know)
  3. we only pass-by-reference and pass-by-value (more on that... now!)

So, we have pass-by-reference and pass-by-value. In the program's memory, when passing by value, the thing that the variable "i" is pointing to is a literal number. When you pass by reference, the thing that the variable "i" would point to would be the memory location of some other object.

What does this mean?

line 1. i = newObject();
line 2. i = newObject();

after line 1. the program will make a little home in memory for "i", and it will give that a locatiion - i don't know, some ram value (say. i don't really know the under-the-hood javascript). Then, in that value, it will put another value. So we have:

i ->0x67->0x68

so the program knows that, when it sees the value "i", it goes to memory location 0x67, and gets the value 0x68. It also knows that THAT value is pointing to another memory location. In which case we have

...
0x67 -> 0x68 (in bytes)
0x68 -> some byte representation of a newObject()
0x69 -> i don't know! some more bytes. they're not important right now.
...

After line 2. runs, we'd have

i ->0x67->0x69 (in bytes)

and

0x69 -> some byte representation of a newObject()

to give:

...
0x67 -> 0x68 (in bytes)
0x68 -> some byte representation of a newObject()
0x69 -> some byte representation of a newObject()
...

Now, as it happens, if you were to so an equality on the two new Ojbects, you'd fine they're the same ( i think ). What is important to note here, is that after the second line, the value of i has changed, from the first memory location, to the second.

SO, if you did this:

i = new Object()
j = i
i = new Object()

you would get:

i ->0x67->0x68->byte fora new Object
j ->0x69->0x68-> byte fora new Object
i ->0x67->0x70-> byte foranother new Object. Same bytes as above.

See? "j" gets the actual byte values in "i", which means "j" gets the value 0x68, which is the memory location for a new object. After the third line, j has the location of the first object, and "i" has the location of the second object.

HOWEVER, any changes you make to j - say

j.x = "moo"

won't appear in the object that "i" is pointing to. It will only appear in "j" object. Because, remember, j is pointing to a totally different object in a totally different location from the object i is pointing to.

Ok, with that out of the way, let's swing back to pass-by-value.

Here

i = 6;j = i;i = 7;

so we get

i ->0x67->6 (well, the bytes for6, anyway)
j ->0x68->6 (again, bytes for6. the same bytes. We're pointing to i here)
i ->0x67->7

AND, if we were to look at j?

j ->0x68->6

so, because we updated "i", we do not also update "j", get it? The bytes are being held, j get its own copy of the bytes here.

OKAY, so with all the above out of the way, let's check out your code:

functionfun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( function() {return i} );
    }
    return result;
}

right, so in the for loop, you are making a function that holds a reference to i, and the action of the for loop is to increment i.

You're probably going "hold up!" or something - do you think internally like that? - because i just noted how the data is passed by value for integers (or numbers, generally). Problem here is this:

From this page: https://developer.mozilla.org/en/JavaScript/Guide/Closures

A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.

So... and i pasted the above for a reason - a closure is an object that is a function (fun1, say) and the environment in which the function was created. And the environment? It consists of the local variables (ooh, how about i?) in scope.

So, to be blunt... just as i is still in scope the entire time of that for loop, that same "i" is in scope for each and every closure we generate. And that i? that i is getting its value updated. You're not passing the snapshot of i, you're passing, as near as i can determine, the reference object itself. It is no longer pass-by-value, we're kinda sorta back to pass by reference.

So, in memory, we have

i ->0x67->0
i ->0x67->1
i ->0x67->2
i ->0x67->3
i ->0x67->4
i ->0x67->5 (yeah, the loop increments i, then fails the test of i <5. but is still 5)

in your array, you're putting a function that just spits back the value of i. But you're incrementing i all the time. Each function you put in that array is going to spit out "5".

To take it one step further...

functionfun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( function() {return i++;} );
    }
    return result;
}

alert(fun1()[0]());
alert(fun1()[0]());
alert(fun1()[4]());

Here, we're testing to see if we can "keep" changes outside the initial environment. If you run this, you won't see any changes stored. In the first example, we increment "i" to 6 - but when we run the example again, i is again 5 - the increment was not kept.

Finally, trying on another array function shows the value wasn't incremented there either.

So, for you, what you want is to somehow freeze in time the value of i at the moment it enters the array

And that's pretty tricky.

functionfun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( eval("(function() { return " + i+";})"));
    }
    return result;
}

Where "eval" is a fancy javascript function that creates code to be run at the time it is run - dynamically, i mean. And i surround it by parenthesis because

Why does JavaScript's eval need parentheses to eval JSON data?

I don't know a super amount about eval though!

Next up, one of the other solutions shows this:

(function(x){
    result.push( function() {return x} );
})(i);

which is placed within the context of the for loop - to give

functionfun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(x){
            result.push( function() {return x} );
        })(i);
    }
    return result;
}

What this does is create a new environment for the closure to exist within. And in this environment, "x" is fixed forever and ever as the value that i currently (ie at point of instantiation) is. So when our array tries to access a given closure, it exists within a new environment where x is fixed. If we'd done this instead:

functionfun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(){
            result.push( function() {return i;} );
        })();
    }
    return result;
}

Then we'd run into the same problem. The "i" still exists in the environment (or scope, if you will), of the loop, so it is going to get picked up, no matter how many times you stick it in functions.

If anyone reads this and thinks what i've written is bollocks, please let me know! I'm kinda keen to hear how js does memory management stuff.

Post a Comment for "Javascript Puzzle: Scope"