Rompecabezas de JavaScript: Alcance

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

console.log(fun1()[0]()) // returns 5?

Shouldn't the first element of that array return a function that returns '0'?

preguntado el 30 de enero de 12 a las 19:01

I've updated my answer... hopefully it helps clarify things a bit. -

+1 to everyone for answering! Because you're all correct, so you should all be winners! -

6 Respuestas

No, it should return 5.

The function still have the reference to i, which is 5 after for.

Respondido el 30 de enero de 12 a las 23:01

What I don't understand is WHY. The function inside is still inside the 'for' loop, not outside of it, so why does it return i's value AFTER the loop? - user1019031

This happens because your function (closure) maintains a

  • referencia a i

más bien que

  • una instantánea de 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 VALORAMOS en vez de referencia? Luckily, numeric parameters are passed by VALORAMOS... so, you can avoid your issue by passing i to a second function...(violín):

function fun1(){
    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]())

[Editar] Let's take the loop out of the equation (violín):

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

So, to "fix" this (violín):

var i = 1;
var fn = (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 x
fn(); // this will alert 1

contestado el 23 de mayo de 15 a las 16:05

closures are dope. Also entirely confusing. Why does this work? - bharal

Funciona porque cuando i is passed to the self-executing anonymouse function it's passed by value rather than reference... so, while the inner-closure has a reference to x, x will always have whatever value i had during that particular iteration. - canon

Let's break down what happens step by step:

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

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

  • Notice that the anonymous function returning i is no invocado throughout the execution.

  • El for loop ends with the value of i "Ser" 5.

  • Invocando fun1()[0] devuelve una matriz result[] which has a function that stores the referencia a i, no la VALORAMOS of i.

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

Respondido el 08 de enero de 15 a las 01:01

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

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

Respondido el 30 de enero de 12 a las 23:01

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.

Respondido el 30 de enero de 12 a las 23:01

What I don't understand is WHY. The function inside is still inside the 'for' loop, not outside of it, so why does it return i's value AFTER the loop? - user1019031

This has to do with the way how Javascript Closure is formed. You may want to read more details about the closure. Here is a useful link: jibbering.com/faq/notes/closures - Gz Zheng

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.

¿Qué significa esto?

line 1. i = new Object();
line 2. i = new Object();

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 new Object()
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)

y la

0x69 -> some byte representation of a new Object()

dar:

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

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()

obtendrías:

i -> 0x67 -> 0x68 ->byte for a new Object
j -> 0x69 -> 0x68 -> byte for a new Object
i -> 0x67 -> 0x70 -> byte for another 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, con y eso out of the way, let's swing back to pass-by-value.

Aquí

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

así que obtenemos

i -> 0x67 -> 6 (well, the bytes for 6, anyway)
j -> 0x68 -> 6 (again, bytes for 6. 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:

function fun1(){
    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:

De esta pagina: 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 instantánea 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...

function fun1(){
    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.

function fun1(){
    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

¿Por qué eval de JavaScript necesita paréntesis para evaluar datos JSON?

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

function fun1(){
    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 Un nuevo environment for the closure to exist within. And in this environment, "x" is fixed forever and ever as the value that i actualmente (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:

function fun1(){
    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" posible 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.

contestado el 23 de mayo de 17 a las 15:05

You're wrong in one example: <br><br> i = 6; j = i; i = 7; so we get i -> 0x67 -> 6 (well, the bytes for 6, anyway) j -> 0x68 -> 0x67 -> 6 (again, bytes for 6. the same bytes. We're pointing to i here) i -> 0x67 -> 7 AND, if we were to look at j? j -> 0x68 -> 0x67 -> 7<br><br> j will be 6 after this. JavaScript copies numbers and strings. It only creates references when using other types (Datentypen = types?). So it will copy the value from i, because the typ is an number (JavaScript only has number, no int, float, double, ...). - user1150525

amended - let me know if there are still flaws - bharal

I think its ok now (well, I'm just an underage student, no expert), except one little thing, but this doesn't matter here: ---- line 1. i = new Object(); line 2. i = new Object(); ---- In this example, the first object is deleted. JavaScript has a kind of garbage collector. So 0x68 will have the Zero-Flag (or whatever) - user1150525

I'm hoping you mean "undergrad", and you're not breaking laws in your home country by illegally studying programming under the age of reason. You're probably right about the garbage collection - although if it follows java style gc it is going to wait for some memory to fill up - but i think that adding gc to this will just make it too complicated. - bharal

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.