【问题标题】:Why does JavaScript Array.push not use variable if the pushed element is not a primitive如果推送的元素不是原始元素,为什么 JavaScript Array.push 不使用变量
【发布时间】:2018-10-15 01:40:33
【问题描述】:

考虑一下这个经典的 JavaScript 闭包函数。我了解关闭是如何展示的。我知道内部函数关闭了变量 i,即 3。

我不明白为什么数组应该包含变量 i,而我们所做的只是将一个函数推入一个数组,其中 i 具有来自 for 循环的值。

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

否则,这将返回数组的正确(imo)内容:

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

【问题讨论】:

  • 变量i 被代码创建的所有函数共享​​>。在循环结束时,i 的值是 3,所以这就是你调用函数时得到的值。
  • 使用for (let i=0...,它可以工作,因为它为i 创建了一个本地范围。
  • 它记录了对i引用,而不是它被推送时的。所有三个引用都是相同的。
  • 因为你推送的是一个函数,而不是函数的结果。你所说的只有在代码是这样的情况下才有效:arr.push(function(){return i;}()); 然后你将创建一个函数,立即调用它并将 i 的返回值添加到数组而不是函数本身。 Console.log() 不返回任何内容,它只记录到控制台。所以它永远不会将结果返回到数组中。这听起来有点像你在混淆闭包、立即调用的函数和返回值。

标签: javascript arrays variables for-loop closures


【解决方案1】:

我认为循环出于某种原因增加了混乱。如果展开该循环,它可能会更直观。

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

所以你可以看到我们将三个函数推入数组,在这之间,我们增加了i。您还可以看到所有三个函数都在“查看”同一个 i 变量。

变量在函数被调用之前不会被读取,所以因为它们都在“查看”同一个变量,所以它们在最终调用时自然会给出相同的结果。由于i 在任何调用之前增加了三倍,因此返回的值将是3


作为练习,更改每个函数以在其中添加另一个i++。你会看到他们不仅都读取同一个变量,而且他们都可以变异同一个变量。

【讨论】:

  • 很有意义。谢谢。是否正确地说,在函数调用之前,函数内部数组的内容是:[fn(){console.log(0)}, fn(){console.log(0)}, fn(){console.log(2)}],而作用域上下文仍然存在?
  • 并非如此。这意味着i 的值被复制到函数中。相反,函数只能引用i,所以它更像[fn(){console.log(i)}, fn(){console.log(i)}, fn(){console.log(i)}] 所以当每个函数被调用时,它使用i引用来读取@的当前值987654332@.
  • ...只要至少有一个指向i 的函数继续存在,buildFunctions 调用的范围就会继续存在
【解决方案2】:

arr.push(i) 将原始值传递给.push,值分别为012。该值与此处的i 解除关联;你不是在推i,你是在推012

arr.push(function () { console.log(i) }) 推送一个函数,该函数在内部引用一个变量。在您调用该函数时,该变量的值恰好是3(?这是要理解的关键句。)

请注意,推送函数和推送数字之间没有根本区别。两者都只是按值传递。只是在一种情况下,值是一个数字,而在另一种情况下,值是一个函数。见Is JavaScript a pass-by-reference or pass-by-value language?

【讨论】:

  • 我几乎把这个问题标记为闭包问题的重复,但意识到它的不同,感谢你的关键位。
【解决方案3】:

当你调用函数时,i 的值变成了 3arr.push 内部的函数引用了它。

块范围let 会给你预期的结果:

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

【讨论】:

  • 这进一步提出了问题为什么?它并没有真正回答OP的问题。
【解决方案4】:

我不明白为什么数组应该包含变量 i

没有。

数组包含三个函数。

这些函数中的每一个都关闭(相同的)变量i。注意:i不是当时i的值

您调用任何函数时(循环结束后),它们会读取该变量的值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-04
    • 2015-06-11
    • 1970-01-01
    • 1970-01-01
    • 2012-08-31
    • 2020-11-24
    • 1970-01-01
    • 2021-12-25
    相关资源
    最近更新 更多