为什么在JavaScript中更改数组会影响数组的副本?

Dan*_*don 70 javascript

我写了以下JavaScript:

var myArray = ['a', 'b', 'c'];
var copyOfMyArray = myArray;
copyOfMyArray.splice(0, 1);
alert(myArray); // alerts ['b','c']
alert(copyOfMyArray); // alerts ['b','c']

var myNumber = 5;
var copyOfMyNumber = myNumber;
copyOfMyNumber = copyOfMyNumber - 1;
alert(myNumber); // alerts 5
alert(copyOfMyNumber); // alerts 4        
Run Code Online (Sandbox Code Playgroud)

此代码声明一个变量myArray并将其设置为数组值.然后它声明第二个变量copyOfMyArray并将其设置为myArray.它执行操作copyOfMyArray,然后警告两者myArraycopyOfMyArray.不知何故,当我执行操作时copyOfMyArray,似乎执行相同的操作myArray.

然后代码使用数字值执行相同的操作:它声明一个变量myNumber并将其设置为数字值.然后它声明第二个变量copyOfMyNumber并将其设置为myNumber.它执行操作copyOfMyNumber,然后警告两者myNumbercopyOfMyNumber.在这里,我得到预期的行为:对不同的价值观myNumbercopyOfMyNumber.

数组和JavaScript中的数字有什么区别,它似乎更改数组会更改数组副本的值,而更改数字不会更改数字副本的值?

我猜测由于某种原因,数组是通过引用引用的,而数字是按值引用的,但为什么呢?我如何知道其他对象的行为?

Fel*_*ing 95

JavaScript中的数组也是一个对象,变量只包含对象的引用,而不是对象本身.因此,两个变量都引用同一个对象.

您与数字示例的比较不正确顺便说一句.您为其分配了一个新值copyOfMyNumber.如果为其分配新值copyOfMyArray也不会更改myArray.

您可以使用slice [docs]创建数组的副本:

var copyOfMyArray = myArray.slice(0);
Run Code Online (Sandbox Code Playgroud)

但请注意,这只会返回一个浅表副本,即不会克隆数组中的对象.

  • @Rice:不,我只是编辑以澄清。如果你想要一个深拷贝,你必须自己写一些东西。但我相信你会找到一个脚本来做到这一点。 (2认同)

Poi*_*nty 20

嗯,唯一可能的答案 - 也就是正确答案 - 是你实际上并没有复制数组.当你写作

var copyOfArray = array;
Run Code Online (Sandbox Code Playgroud)

您将同一数组的引用分配给另一个变量.换句话说,他们都指向同一个物体.


Sal*_*ore 12

所以这里的每个人都做了很好的解释为什么会这样 - 我只想放弃一条线让你知道我是如何解决这个问题的 - 非常容易:

thingArray = ['first_thing', 'second_thing', 'third_thing']
function removeFirstThingAndPreserveArray(){
  var copyOfThingArray = [...thingArray]
  copyOfThingArray.shift();
  return copyOfThingArray;
}
Run Code Online (Sandbox Code Playgroud)

这是使用... spread语法.

传播语法源

编辑:至于为什么这样,并回答你的问题:

数组和JavaScript中的数字有什么区别,它似乎更改数组会更改数组副本的值,而更改数字不会更改数字副本的值?

答案是在JavaScript中,数组和对象是可变的,而字符串和数字以及其他基元是不可变的.当我们做一个如下任务:

var myArray = ['a', 'b', 'c']; var copyOfMyArray = myArray;

copyOfMyArray实际上只是对myArray的引用,而不是实际的副本.

我会推荐这篇文章,什么是不可变和可变数据结构?,深入挖掘这一主题.

MDN词汇表:可变

  • 正是我在寻找什么. (2认同)

But*_*uts 9

我发现这是对对象或数组进行深度克隆的最简单方法:

const objectThatIWantToClone = { foo: 'bar'};
const clone = JSON.parse(JSON.stringify(objectThatIWantToClone));
Run Code Online (Sandbox Code Playgroud)

通过对其进行字符串化,我们将其复制为不可变的,然后可以将其转换回JSON。

https://codepen.io/Buts/pen/zWdVyv


Bos*_*h99 5

克隆对象 -

A loop / array.push产生与array.slice(0)或相似的结果array.clone().值都是通过引用传递的,但由于大多数原始数据类型都是不可变的,因此后续操作会产生所需的结果 - "克隆".当然,对象和数组不是这样,它允许修改原始引用(它们是可变类型).

请看以下示例:

const originalArray = [1, 'a', false, {foor: 'bar'}]
const newArray = [];

originalArray.forEach((v, i) => {
    newArray.push(originalArray[i]);
});

newArray[0] = newArray[0] + 1;
newArray[1] = 'b';
newArray[2] = true;
newArray[3] = Object.assign(newArray[3], {bar: 'foo'});
Run Code Online (Sandbox Code Playgroud)

在newArray索引上运行的操作都会产生所需的结果,但final(object)除外,因为它是通过引用复制的,它也会改变originalArray [3].

https://jsfiddle.net/7ajz2m6w/

请注意,array.slice(0) and array.clone()这也受到同样的限制.

解决此问题的一种方法是在推送序列期间有效地克隆对象:

originalArray.forEach((v, i) => {
    const val = (typeof v === 'object') ? Object.assign({}, v) : v;
    newArray.push(val);
});
Run Code Online (Sandbox Code Playgroud)

https://jsfiddle.net/e5hmnjp0/

干杯


Omp*_*rma 5

浅复制的问题是,所有对象都不会被克隆,而是会获得引用。因此 array.slice(0) 仅适用于文字数组,但它不会对对象数组进行浅复制。在这种情况下,一种方法是..

var firstArray = [{name: 'foo', id: 121}, {name: 'zoo', id: 321}];
var clonedArray = firstArray.map((_arrayElement) => Object.assign({}, _arrayElement));  
console.log(clonedArray);
// [{name: 'foo', id: 121}, {name: 'zoo', id: 321}]  // shallow copy
Run Code Online (Sandbox Code Playgroud)