调用 javascript 函数两次的问题

issue with calling a javascript function twice

我正在尝试编写一个相当简单的 Javascript 函数,但在迭代该函数时遇到了我无法理解的行为。

我将问题归结为以下情况。我想编写一个函数,它将一个由数组数组组成的数组作为输入,例如A = [[[1]]]。我不知道这方面的标准术语,所以我将主数组称为 "level 0",它的元素是 "level 1" 的数组。我会说 1 级数组由 "level 2" 的数组组成。二级数组由整数组成。

该函数在输入 A(0 级数组)时执行以下操作:

  1. 创建一个空数组L;
  2. 对于 A 中的每个级别 1 数组 M
    • M;
    • 中每个 2 级数组中的每个整数条目加一
    • M 的两个副本添加到 L
  3. return L.

这是我的代码:

function myFunc(A){
  var L = [];
  for(var a=0; a<A.length; a++){
    var M = A[a].slice(0);
    for(var i=0; i<M.length; i++){
      for(var j=0; j<M[i].length; j++){
        M[i][j]++;
      }
    }
    for(var s=0; s<2; s++){
      var N = M.slice(0);
      L.push(N);
    }
  }
  return(L);
}

现在我测试一下:

var A = [[[1]]];

A = myFunc(A)

在此之后,我得到 A = [[[2]],[[2]]],这是我所期望的。但是,假设我迭代它:

var A = [[[1]]];

A = myFunc(A);

A = myFunc(A);

然后我期望获得A = [[[3]],[[3]],[[3]],[[3]]],但我却得到了A = [[[4]],[[4]],[[4]],[[4]]]

另一方面,如果我 运行 myFunc([[[2]],[[2]]]),我确实得到了预期的 [[[3]],[[3]],[[3]],[[3]]]

我不明白这种差异是从哪里来的。

问题是行:

M[i][j]++;

Node 将其保留为对 A 的切片的引用,您在执行此操作时会清楚地看到它:

x = [[[1]]];
myFunc(x);
myFunc(x);
console.log(x); // ---> [[[3]]]

对于浅拷贝,您必须使用 JSON.parse(JSON.stringify()) 技巧,并证明 M 是问题所在;在 M = A[a].slice(0); 之后添加此行可以解决问题。

M = JSON.parse(JSON.stringify(M))

Mozilla 关于 Array.prototype.slice() 的文档:

For object references (and not the actual object), slice copies object references into the new array. Both the original and new array refer to the same object. If a referenced object changes, the changes are visible to both the new and original arrays.

Source

这就是为什么,因为当你这样做时M[i][j],更深一层的数组仍然被外部引用。

既然已经指出了造成差异的原因,我只想补充一下为什么

var A = [[[1]]];

A = myFunc(A);
A = myFunc(A);

给出了与

不同的结果
myFunc([[[2]],[[2]]])

在做myFunc([[[2]],[[2]]])时,你基本上是在做myFunc(new Array(new Array(new Array(2))),(new Array(new Array(2)))))

所以当 V8 引擎提升这条线时

var M = A[a].slice(0);

它会将 A[a].slice(0) 解释为

new Array(new Array(new Array(2))),(new Array(new Array(2))))[a].slice(0)

因此每次调用时都会得到 2(来自新数组)。

如果您使用日志检查函数,可以看到这一点:

function myFunc(A){
  var L = [];
  console.log("A is"+ JSON.stringify(A));
  for(var a=0; a<A.length; a++){
    var M = A[a].slice(0);
    console.log("M is"+ JSON.stringify(M) + " while A is" +JSON.stringify(A),a); 
    for(var i=0; i<M.length; i++){
      for(var j=0; j<M[i].length; j++){
        M[i][j]++;
      }
    }
    for(var s=0; s<2; s++){
      var N = M.slice(0);
      L.push(N);
      console.log("L.push:"+ JSON.stringify(N));
    }
  }
  console.log("the end" + JSON.stringify(L), JSON.stringify(A));
  return(L);
}

var A = [[[1]]];
A = myFunc(A);
A = myFunc(A);
var t = myFunc([[[2]],[[2]]]);

如果将 var M = A[a].slice(0); 替换为

var M = new Array(new Array(new Array(2))),(new Array(new Array(2))))[a].slice(0)

和运行通过没有A的函数,你会看到[[[3]],[[3]],[[3]],[[3]]].

正如其他人已经提到的,问题是 M.slice 调用只进行了浅拷贝。他们还为如何进行深度复制提供了很好的解决方案。不过,我建议您真的根本不需要复制:

var L = [];
for (var iLevel1 = 0; iLevel1 < A.length; iLevel1++)
{
    for (var iLevel2 = 0; iLevel2 < A[iLevel1].length; iLevel2++)
    {
        for (var iLevel3 = 0; iLevel3 < a[iLevel1][iLevel2].length; iLevel3++)
           L.push(a[iLevel1][iLevel2][iLevel3] + 1); 
    }
}

return L.concat(L);