为什么修改array.slice()也会改变原始数组?

7

我试图修改一个复制的数组,而不改变原始数组。我尝试了使用slice()方法,但它没有像预期的那样工作:

//toFill holds the values I want to add into an array named secondArr, 
//replace every empty sting within secondArr with 0
var toFill = [0, 0, 0, 0];
var mainArr = [
  [" ", 1, 1, 1],
  [" ", 1, 1, 1],
  [" ", 1, 1, 1],
  [" ", 1, 1, 1]
];
var secondArr = mainArr.slice(0,4);
//this function returns a 1D array, stores the indices of all empty strings within an array
function findBlankSpaces(secondArr) {
  var emptyIndices = [];
  var innerArrLen = secondArr.length;
  var outterArrLen = secondArr[0].length;
  for (var i = 0; i < innerArrLen; i++) {
    for (var j = 0; j < outterArrLen; j++) {
      if (secondArr[i][j] == " ") {
        emptyIndices.push([i, j]);
      }
    }
  }
  return emptyIndices;
}

//this function returns the modified array, with empty spaces replaced with 0s
function fillWithZero(secondArr, toFill) {
  var emptyIndices = findBlankSpaces(secondArr);
  for (var i = 0; i < emptyIndices.length; i++) {
    secondArr[emptyIndices[i][0]][emptyIndices[i][1]] = toFill[i];
  }
}
//consoles
console.log(fillWithZero(secondArr, toFill));
console.log(mainArr);
//expected output in console is [[" ", 1,1,1], [" ",1,1,1], [" ",1,1,1], [" ",1,1,1]];
//actual output is [[0,1,1,1], [0,1,1,1], [0,1,1,1], [0,1,1,1]];
//I didn't modify mainArr, but only modified secondArr, why that mainArr also affected?

我没有修改mainArr,只是使用slice()创建了一个副本,但为什么当其副本发生变化时,它也会改变?

问题是:有没有办法阻止这种情况发生,或者如何调用不含任何0的mainArr,我希望mainArr保持不变。谢谢。


2
我并没有修改 mainArr,只是通过使用 slice() 创建了一份副本,但为什么在副本更改时它会发生变化呢?请参考 .slice(): "slice() 方法返回一个新的数组对象,这个对象是从 begin 到 end(不包括 end)选择的数组的浅拷贝。原始数组将不会被修改。" 你需要指定索引 (arr.slice([begin[, end]]))。 - Mukyuu
1
数组和对象在复制时总是保留对父级的引用。因此,无论何时发生更改,都会影响两者。 - Akhil Aravind
1
请执行以下代码:var secondArr = JSON.parse(JSON.stringify(mainArr)); - Ponleu
5个回答

7

正如在Slice MDN中所示,slice方法返回调用它的数组的浅拷贝。深度复制讨论了JS中几种深度复制对象的方式。它提到的一种很好的方法是

var secondArr = JSON.parse(JSON.stringify(mainArr))

这将把您的数组转换为JSON格式,然后解析成一个新的对象。

4

slice() 方法返回一个新的数组对象,这一对象是对原始数组的浅拷贝。

关键词在于 浅拷贝

如果 mainArr 是一个嵌套数组,那么 slice 方法将会返回该嵌套数组的一个副本。但是,该方法所返回的数组中的元素引用的是 mainArr 中相应的元素。因此,当你对这些元素进行修改时,实际上是在修改 mainArr 中的元素。

你可以对 mainArr 进行深度克隆,但更高效的做法往往是只替换需要的部分。

尽管以下代码可行,但其还有改进的余地。其中一种改进方式是使用 immer 库来进行修改。

//replace every empty sting within secondArr with 0
var toFill = [0, 0, 0, 0];
var mainArr = [[" ", 1, 1, 1], [" ", 1, 1, 1], [" ", 1, 1, 1], [" ", 1, 1, 1]];
//this function returns a 1D array, stores the indices of all empty strings within an array
function findBlankSpaces(arr) {
  var emptyIndices = [];
  var innerArrLen = arr.length;
  var outterArrLen = arr[0].length;
  for (var i = 0; i < innerArrLen; i++) {
    for (var j = 0; j < outterArrLen; j++) {
      if (arr[i][j] == " ") {
        emptyIndices.push([i, j]);
      }
    }
  }
  return emptyIndices;
}

//this function returns the modified array, with empty spaces replaced with 0s
function fillWithZero(mainArr, toFill) {
  var emptyIndices = findBlankSpaces(mainArr);
  let newArr = [...mainArr];
  emptyIndices.forEach(indic => {
    newArr[indic[0]] = [
      ...mainArr[indic[0]].slice(0, indic[1]),
      toFill[indic[1]],
      ...mainArr[indic[0]].slice(indic[1] + 1)
    ];
  });
  return newArr;
}
//consoles
console.log(fillWithZero(mainArr, toFill));
console.log(mainArr);


3
如果您将切片方法应用到包含数组的数组上,则子数组会被引用复制。您添加到副本中的任何内容都将添加到包含数组的原始数组中。

slice() 方法返回一个新的数组对象,其中包含从 begin 到 end(不包括 end)选定的数组部分的浅拷贝,其中 begin 和 end 表示该数组中项目的索引。原始数组不会被修改。 - nmanikumar5

2
当复制一个ArrayObject时,它们总是会保留对父级的引用。所以当我们把array a复制到array b时,对b所做的更改也会影响到a,因为b有一个对a的引用。
为了解决这个问题,我们可以使用展开运算符(...)。
请查看以下代码片段:

var a = [1,2,3,4,5,6];
var b = a;
console.log('First array ', a);
console.log('Second array ', b);
// lets remove an element from b and check
b.splice(1,1);
console.log('Second array after splicing', b);
console.log('First array after splicing', a);
// Here value in the index 1 is missing in both the array.
// to prevent this we can use spread operator


var a = [1,2,3,4,5,6];
var b = [...a];
console.log('First array ', a);
console.log('Second array ', b);
// lets remove an element from b and check
b.splice(1,1);
console.log('Second array after splicing using spread operator', b);
console.log('First array after splicing using spread operator', a);


1
需要注意的是,...扩展运算符也只会进行浅拷贝。你可以在这里看到一个例子:https://dev59.com/F1QJ5IYBdhLWcg3wFx1_#54605288 - Code Maniac

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接