如何将一个数组中的所有项目复制到另一个数组中?

81

如何将一个由对象构成的数组中的每个元素复制到另一个数组中,使它们完全独立?

我不想改变一个数组中的元素会影响另一个数组。


数组部分已经在这个问题的答案中得到了解决,对象部分已经在这个问题的答案中得到了解决。我也已经修正了我的回答。*(你可能不记得了,但是你在前五分钟内编辑了你的问题以添加关于对象的说明吗?因为包括我在内的几个人回答时似乎没有看到那部分。)* - T.J. Crowder
至少从标题来看,这些链接的问题是关于克隆,而不是复制值。a = [1, 2, 3], b = [4, 5, 6]。如果你执行 a = b.slice(),你已经复制了b。你没有将b的值复制到a中。a的数组可能被多个地方引用。 - gman
8个回答

120

关键点如下:

  1. 数组中的条目是对象,
  2. 您不希望对一个数组中对象的修改出现在另一个数组中。

这意味着我们不仅需要将对象复制到新数组(或目标数组)中,还需要创建对象的副本

如果目标数组尚不存在...

...可以使用map创建一个新数组,并在进行复制时同时复制对象:

const newArray = sourceArray.map(obj => /*...create and return copy of `obj`...*/);

......其中复制操作是您喜欢复制对象的任何方式,这根据项目的使用情况有很大的不同。有关此主题的详细信息,请参阅该问题的答案。但是举个例子,如果您只想复制对象而不是它们属性所引用的任何对象,您可以使用扩展符号(ES2015+):

const newArray = sourceArray.map(obj => ({...obj}));

这会对每个对象(和数组)进行浅复制。如果需要深层复制,请参见上面链接的问题的答案。

这里有一个使用天真形式的深度复制的示例,它不尝试处理边缘情况,请查看链接的问题以获取边缘情况:

function naiveDeepCopy(obj) {
    const newObj = {};
    for (const key of Object.getOwnPropertyNames(obj)) {
        const value = obj[key];
        if (value && typeof value === "object") {
            newObj[key] = {...value};
        } else {
            newObj[key] = value;
        }
    }
    return newObj;
}
const sourceArray = [
    {
        name: "joe",
        address: {
            line1: "1 Manor Road",
            line2: "Somewhere",
            city: "St Louis",
            state: "Missouri",
            country: "USA",
        },
    },
    {
        name: "mohammed",
        address: {
            line1: "1 Kings Road",
            city: "London",
            country: "UK",
        },
    },
    {
        name: "shu-yo",
    },
];
const newArray = sourceArray.map(naiveDeepCopy);
// Modify the first one and its sub-object
newArray[0].name = newArray[0].name.toLocaleUpperCase();
newArray[0].address.country = "United States of America";
console.log("Original:", sourceArray);
console.log("Copy:", newArray);
.as-console-wrapper {
    max-height: 100% !important;
}

如果目标数组存在...

...而且你想将源数组的内容追加到它上面,你可以使用 push 方法和一个循环:

for (const obj of sourceArray) {
    destinationArray.push(copy(obj));
}

有时候人们真的想要一个“一行代码”的解决方案,即使没有特定的原因。如果你需要这样做,你可以创建一个新的数组,然后使用扩展符将其展开为单个push调用:

有时候人们真的想要一个“一行代码”的解决方案,即使没有特定的原因。如果你需要这样做,你可以创建一个新的数组,然后使用扩展符将其展开为单个push调用:

destinationArray.push(...sourceArray.map(obj => copy(obj)));

1
@user1720860:以上代码确实克隆了数组条目,这也是问题所要求的。如果你有比以上更复杂的情况,我建议你发布一个新问题,包括你正在处理的代码/数组结构,这样人们就可以帮助你解决它。 - T.J. Crowder
如果sourceArray为null,则“apply”在IE8中会失败。 - v-tec
@v-tec:如果其他浏览器也不支持的话,我会有点惊讶。 - T.J. Crowder
更简洁的 push 方法: Array.prototype.push.apply(destinationArray, sourceArray); - Veikko Karsikko
1
destinationArray.push(...sourceArray); 对我来说完美地运作了。 - Kishor Pawar
显示剩余5条评论

35

使用以下方法可以轻松使其工作:

var cloneArray = JSON.parse(JSON.stringify(originalArray));

我在使用 arr.concat()arr.splice(0) 时遇到问题,无法得到一个深度复制的数组。上面的代码片段可以完美地工作。


1
正如在另一条评论中提到的那样,值得注意的是,在JSON.stringify过程中,数组中的undefined和任何函数都将被转换为null,尽管这是克隆数组本身及其所有子元素的最简单方法,以便进一步的更改不会影响原始数组,因此如果上述警告对您的用例没有任何问题,它仍然非常有用。 - fredrivett
这种方式可能是最后的机会,因为这个函数对生产力有很大的影响。 - Yurii Space

16

使用数组字面量展开语法是克隆数组的好方法,这得益于ES2015

const objArray = [{name:'first'}, {name:'second'}, {name:'third'}, {name:'fourth'}];

const clonedArr = [...objArray];

console.log(clonedArr) // [Object, Object, Object, Object]

您可以在 MDN 的文档中找到此复制选项:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Copy_an_array

这也是 Airbnb 的最佳实践之一。https://github.com/airbnb/javascript#es6-array-spreads

注意:ES2015 中的展开语法只能对数组进行一层深度的复制,因此不适用于多维数组的复制。


请注意,该问题涉及到一个对象数组,并且讨论了不希望在新数组中修改对象以在旧数组中进行修改的情况。这并没有复制对象,只是数组,因此不能满足OP的要求。 (我的答案最初也没有解决这个问题。多年后[今天],有人指出了问题的一部分。我无法想象我是如何错过它的,但很多人都错过了。奇怪。(副注:...不是运算符。) - T.J. Crowder
非常有趣,MDN文档以前称其为扩展运算符。我分享的URL仍然是这样的:https://developer.mozilla.org/.../Operators/Spread_operator#Copy_an_array。 无论如何,我已经更新了答案,改为使用扩展语法。 - MauricioLeal
1
是的,MDN是由社区编辑的,在早期,一些与其无关的人认为它是一个运算符。(事实上,我就是那个修复MDN的人。;-)) - T.J. Crowder

5
var clonedArray = array.concat();

请注意,这个问题涉及到一个对象数组,并且讨论了不希望在新数组中修改对象以在旧数组中进行修改的情况。这并没有复制对象,只是数组,因此不能满足提问者的要求。我的回答最初也没有解决这个问题。多年后[今天],有人指出了问题的一部分。我无法想象我当时是怎么错过它的,但很多人都错过了。奇怪。 - T.J. Crowder

3
如果你想要保留引用:
Array.prototype.push.apply(destinationArray, sourceArray);

2
无法处理长度超过65536的数组,因为这是Chrome浏览器中硬编码参数限制的最大值。 - Jack G
1
请注意,这个问题涉及到一个对象数组,并且讨论了不希望在新数组中修改对象以在旧数组中进行修改的情况。这并没有复制对象,只是数组,因此不能满足提问者的要求。我的回答最初也没有解决这个问题。多年后(今天),有人指出了问题的一部分。我无法想象我当时怎么会错过它,但很多人都错过了。奇怪。 - T.J. Crowder

2

有两个重要的注意事项。

  1. 在使用 Angular 1.4.4 和 jQuery 3.2.1(这是我的环境)时,使用 array.concat() 不起作用。
  2. array.slice(0) 是一个对象。因此,如果您执行像 newArray1 = oldArray.slice(0); newArray2 = oldArray.slice(0) 这样的操作,则两个新数组将引用同一个数组,更改其中一个将影响另一个。

或者,使用 newArray1 = JSON.parse(JSON.stringify(old array)) 只会复制值,因此每次都会创建一个新数组。


2
这不是真的。JavaScript array.slice(0) 每次调用都会返回一个新对象,改变一个返回的实例并不会改变其他实例。如果您正在使用更改此行为的“框架”,那么这只是一个错误/糟糕的设计。 - wonder.mice
1
不确定我是否同意@wonder.mice的观点。如果我在浏览器控制台中运行以下代码,则会更改原始数组中的对象:const first = [{test: 'test'}]; const second = first.slice(); second.map(x=>x.test='test2'); 然后,当我检查first时,test的值将已经更改为“test2”。 - Erik van den Hoorn
@ErikvandenHoorn 嗯,是的,数组被复制了,但这是一种“浅”复制,而不是“深”复制——元素仍然引用相同的值。将新项添加到 second 中,first 将不会有它。 - wonder.mice

0

如果你在使用nodeJS,我建议使用concat()。其他情况下,我发现slice(0)效果也很好。


请注意,该问题涉及到一个包含对象的数组,并且谈论了不希望在新数组中修改对象会导致旧对象被修改。这并没有复制对象,只是复制了数组,因此并没有满足OP的要求。我的答案最开始也没有考虑到这一点。多年以后(今天)有人指出了问题的这一部分。我无法想象我当时怎么会错过它,但许多人都错过了。奇怪。 - T.J. Crowder

0

structuredClone 表示一种进行深层克隆的新方法。

const objArray = [{name:'first'}, {name:'second'}, {name:'third'}, {name:'fourth'}];

// Clone it
const clonedArr = structuredClone(objArray);
console.log(clonedArr)


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