繁体   English   中英

Javascript闭包:原始vs参考行为

[英]Javascript closures: primitive vs reference behaviour

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

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?

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

让我们逐个遍历代码,以便更清晰

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"显示警告"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,阵列克[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数组引用。 即使我们使用g数组调用returnFunction并且它只有一个值, 返回的函数也会与引用绑定,而不是在该快照中绑定它。 因此输出打印数组大小为// x:1 - nb of elements in y: 3

执行returnFunction ,第二个和第三个循环returnFunction

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

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

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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