简体   繁体   中英

Why does JavaScript Array.push not use variable if the pushed element is not a primitive

Consider this classic JavaScript closure function. I understand how the closure is exhibited. I understand that the inner function closes in on the variable i, which is 3.

What I dont understand is why the array should contain the variable i, when all we are doing is pushing a function into an array where i has a value from the for loop.

 function buildFunctions() { var arr = []; for (var i = 0; i < 3; i++) { arr.push(function() { console.log(i) }) } return arr; } var fs = buildFunctions(); // [function(){console.log(1)}, ...and so on] //not [function(){console.log(i)} ...and so on] fs[0](); // outputs 3 fs[1](); // 3 fs[2](); // 3 

otherwise, this would return the correct(imo) contents of the array:

function buildFunctions() {
   var arr = [];
   for (var i = 0; i < 3; i++) {
      arr.push(i)
   }
   return arr; // [0, 1, 2]
 }

arr.push(i) passes a primitive value to .push , the values 0 , 1 and 2 respectively. The value becomes disassociated from i here; you're not pushing i , you're pushing 0 , 1 and 2 .

arr.push(function () { console.log(i) }) pushes a function, which internally references a variable. At the time that you call the function, the value of that variable happens to be 3 . (👈 This is the critical sentence to understand.)

Note that there's no fundamental difference between pushing a function and pushing a number. Both are simply passed by value. Just in one case the value is a number and in the other case the value is a function. See Is JavaScript a pass-by-reference or pass-by-value language? .

I think the loop adds confusion for some reason. If you unroll that loop, it likely is more intuitive.

 function buildFunctions() { var arr = []; var i = 0; arr.push(function() { console.log(i) }) i++; arr.push(function() { console.log(i) }) i++; arr.push(function() { console.log(i) }) i++; return arr; } var fs = buildFunctions(); // [function(){console.log(1)}, ...and so on] //not [function(){console.log(i)} ...and so on] fs[0](); // outputs 3 fs[1](); // 3 fs[2](); // 3 

So you can see that we're pushing three functions into the array, and in between, we're incrementing i . You can also see that all three functions are "looking at" the same i variable.

The variable is not read until the function is invoked , so because they're all "looking at" the same variable, they're naturally going to give the same result when finally invoked. And because the i was incremented three times before any of the invocations , the value returned will be 3 .


As an exercise, change each function to add another i++ within. You'll see that no only are they all reading the same variable, but they all can mutate that same variable too.

By the time you call the function the value of i becomes 3 which the function inside arr.push refer.

Block scope let will give you the expected result:

 function buildFunctions() { var arr = []; for (let i = 0; i < 3; i++) { arr.push(function() { console.log(i) }) } return arr; } var fs = buildFunctions(); // [function(){console.log(1)}, ...and so on] //not [function(){console.log(i)} ...and so on] fs[0](); // 0 fs[1](); // 1 fs[2](); // 2 

What I dont understand is why the array should contain the variable i

It doesn't.

The array contains three functions.

Each of those functions closes over the (same) variable i . Note: i not the value of i at the time.

When you call any of the functions (which is after the loop has finished ), they read the value of that variable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM