Javascript闭包:原始vs参考行为

时间:2015-09-26 18:06:28

标签: javascript

我希望有人可以向我解释下面的代码中发生了什么。我发现很难绕过为什么这个闭包以不同方式处理原语和引用。我希望我在这里遗漏一些明显的东西。

function returnFunction(x, y) {
  return function() {
      alert("x:" + x + " - nb of elements in y:" + y.length);
  };
}

var f = 0;
var g = [];
var h = [];

for(var i = 0; i < 3; i++) {
  f += 1;
  g.push(i);
  h.push(returnFunction(f, g));
}

for(var i = 0; i < 3; i++) {
  h[i]();
}

// this is what gets returned
// x:1 - nb of elements in y: 3
// x:2 - nb of elements in y: 3
// x:3 - nb of elements in y: 3

// why do x and y get treated differently by js in this case?

6 个答案:

答案 0 :(得分:5)

  

这是因为,上下文绑定到引用,而不是绑定   调用/创建时的引用快照。

让我们一步一步地浏览代码,以便更清晰

for(var i = 0; i < 3; i++) {
  f += 1;
  g.push(i);
  h.push(returnFunction(f, g));
}

以上循环执行3次,每次在g数组和h数组中放置一些值。

所以让我们进去,填充什么值。 在此之前,请清楚以下代码。

function returnFunction(x, y) {
  return function() {
      alert("x:" + x + " - nb of elements in y:" + y.length);
  };
}

调用上面的函数一次,将返回一个函数,再次调用它意味着你第一次收到的任何引用,它会显示一个警告。简而言之,(returnFunction(5,[4,5,6])())会显示警告 "x:5 - nb of elements in y: 3"。 //看起来y正在采用数组参数,因为在alert中我们有y.length属性。

填充值

loop_number:

1。 - - - - f = 1 - - - - g = [1] - - - - h [用(1,array-g [1])调用时返回函数]

2。 - - - - f = 2 - - - - g = [1,2] - - - - h [调用时返回函数(1,array-g [1] ),用(2,array-g [1,2])]

调用时返回函数

3。 - - - - f = 3 - - - - g = [1,2,3] - - - - h [调用时返回函数(1,array-g [ 1]),用(2,array-g [1,2])调用返回函数,用(3,array-g [1,2,3])调用返回函数]

<强>最后

for(var i = 0; i < 3; i++) {
  h[i]();
}

在上面的解释中,我们循环遍历h数组,即我们在数组h中在循环-3处获得的值。它具有已具有上下文值的内部函数。即每次调用时它知道什么是x和什么是y。 请记住,第二个参数是我们发送的h数组引用。

因此,如果我们执行h[i]()之类的函数,它将使用第一个x的主值和y数组引用执行。即使我们使用returnFunction数组调用g并且它只有一个值,返回的函数也会与引用绑定,而不是在该快照中绑定它。 因此输出的打印数组大小为// x:1 - nb of elements in y: 3

执行returnFunction时第2和第3个循环也是如此。

答案 1 :(得分:3)

原始类型包含实际数据,而引用类型仅包含内存地址(指向对象数据所在的内存位置的指针)。

因此,当您想要检查数组的length字段(这是一个引用类型)时,首先需要找到对象数据所在的内存地址(您查看{ {1}}变量),你去那个地址,最后看看数据。

现在,当您调用函数时,对于每个参数,都会生成其值的副本,并将其存储在函数作用域中可用的局部变量中。因此,每次将数组作为参数传递时,数据所在的内存地址的副本都存储在局部变量中(仅复制内存地址,而不是整个对象)。

所以,回答你的问题:不,原语和引用类型在传递给函数时不会被区别对待。在两种情况下都会进行复制,除了基元包含实际数据,而引用不包含实际数据,它们包含一个地址(指向实际数据的指针)。当您按照地址获取数据时,数据可能在您复制地址和检查数据的时间之间被修改。

答案 2 :(得分:0)

g数组的定义超出了returnFunction的范围。 Javascript总是按值传递。在引用的情况下,引用本身被复制,但它仍然是原始数组对象,因此最终值重复3次。

答案 3 :(得分:0)

那是因为内部函数只有对数组的引用,并且在调用函数之前更改了该数组。

您可以通过仅访问内部函数中的原始值来修复它。

function returnFunction(x, y) {
  var len = y.length; // Primitive value
  return function() {
      alert("x:" + x + " - nb of elements in y:" + len);
  };
}

或者,您可以复制数组。即使原始数组在外部更改,也不会更改副本。

function returnFunction(x, y) {
  y = y.slice(); // Copy it
  return function() {
      alert("x:" + x + " - nb of elements in y:" + y.length);
  };
}

答案 4 :(得分:0)

这是因为闭包持有对参数的引用。在调用函数之前,g数组会被修改。为避免这种情况,您需要复制传入的数组,并将其存储在函数中。

答案 5 :(得分:0)

g数组被修改3次,从不复制。原语按值传递并复制。因此,当定义闭包时,它们保持前一循环的值。最后一个循环打印相同数组的长度3次。