Javascript闭包:原始行为与引用行为

Javascript closures: primitive vs reference behaviour

本文关键字:引用 原始 闭包 Javascript      更新时间:2023-09-26

我希望有人能给我解释一下下面的代码是怎么回事。我发现很难理解为什么这个闭包以不同的方式对待原语和引用。我希望我错过了一些明显的东西。

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"。//看起来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数组,即在上面解释的循环-3中我们在数组h中的值。它的内部函数已经有了上下文值。也就是说,每次调用时,它都知道xy是什么。记住,第二个参数是我们发送的h数组引用。

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

在执行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次。