JavaScript 中克隆数组的方法

JavaScript 中克隆数组的方法

JavaScript 可以做很多事情,现在我们了解处理数组克隆。

扩展操作符(浅拷贝)

自从 ES6 推出以来,这是最受欢迎的方法。当在类似 React 和 Redux 的框架中使用,扩展操作符语法简洁且难以置信的好用。

const numbers = [1, 2, 3];
const numbersCopy = [...numbers];

注意:该方法不是安全地复制多维数组,Array/object 类型的值在复制时使用的是值的引用实例。

于是,良好的情况:

numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// numbers 是独立的

不好的情况:

const nestedNumbers = [[1], [2]];
const numbersCopy = [...nestedNumbers];

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 它们两个都发生改变,因为它们共享引用

for() 循环(浅拷贝)

相信这是最流行的,毕竟这是编程圈子里面通用的方法。

纯净或不纯净,陈述性或命令性,就可以完成工作!

const numbers = [1, 2, 3];
const numbersCopy = [];

for (let i = 0; i < numbers.length; i++) {
  numbersCopy[i] = numbers[i];
}

这不能安全地复制多维数组。由于使用的是 = 运算符,它将通过引用而不是值分配对象/数组。

良好的情况:

numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// numbers 是独立的

不好的情况:

const nestedNumbers = [[1], [2]];
const numbersCopy = [];

for (let i = 0; i < nestedNumbers.length; i++) {
  numbersCopy[i] = nestedNumbers[i];
}

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 它们两个都发生改变,因为它们共享引用

while() 循环

for 相似。

const numbers = [1, 2, 3];
const numbersCopy = [];
let i = -1;

while (++i < numbers.length) {
  numbersCopy[i] = numbers[i];
}

注意:这同样是通过引用而不是值分配对象/数组。

良好的情况:

numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// numbers 是独立的

不好的情况:

const nestedNumbers = [[1], [2]];
const numbersCopy = [];

let i = -1;

while (++i < nestedNumbers.length) {
  numbersCopy[i] = nestedNumbers[i];
}

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 它们两个都发生改变,因为它们共享引用

Array.map(浅拷贝)

我们想到 map 函数。在数学领域map 是将一组成另一种类型的设定,同时保持结构的概念。

众所周知,Array.map 意味着每次都返回一个相同长度的数组。要使数字列表加倍,可以使用 Array.map 配合 double 函数。

const numbers = [1, 2, 3];
const double = (x) => x * 2;

numbers.map(double);

怎样克隆呢?

确实,这篇文章是关于克隆数组,为了复制数组,只需要在 map 的回调函数中返回元素自身。

const numbers = [1, 2, 3];
numberCopy = numbers.map((x) => x);

注意:这同样是通过引用而不是值分配对象/数组。

Array.filter(浅拷贝)

Array.filter 函数像 map 那样返回一个数组,但它通常不会生成相同长度数组。

你是怎样过滤数据中的偶数的呢?

[1, 2, 3].filter((x) => x % 2 === 0);
// [2]

输入的数组的长度是 3,结果的长度是 1.

如果 filter 的回调函数总是返回 true,于是同样实现了复制:

const numbers = [1, 2, 3];
const numbersCopy = numbers.filter(() => true);

每一个数组元素都测试通过,故得到返回,实现数组复制。

注意:这同样是通过引用而不是值分配对象/数组。多维数组的克隆复制不安全。

Array.reduce(浅拷贝)

我对使用 Array.reduce 来克隆数组感到一点感伤,因为它是比其它方法更强大。

const numbers = [1, 2, 3];

const numbersCopy = numbers.reduce((newArray, element) => {
  newArray.push(element);

  return newArray;
}, []);

Array.reduce 通过轮询列表来转换初始值。上面的初始值是一个空数组,然后我们使用每一个元素填充它。该数组在回调函数中必须返回以用于下一次的迭代。

注意:这同样是通过引用而不是值分配对象/数组。多维数组的克隆复制不安全。

Array.slice(浅拷贝)

Array.slice 基于你提供的开始/结束索引返回数组的浅拷贝。如果我们想要开始的 3 个元素:

[1, 2, 3, 4, 5].slice(0, 3);
// [1, 2, 3]
// Starts at index 0, stops at index 3
// 在索引 0 开始索引 3 结束

如果想要各道所有的元素,不设置任何参数:

const numbers = [1, 2, 3, 4, 5];
const numbersCopy = numbers.slice();
// [1, 2, 3, 4, 5]

注意:这是浅拷贝,同样是通过引用而不是值分配对象/数组。多维数组的克隆复制不安全。

JSON.parse 和 JSON.stringify(深拷贝)

JSON.stringify 转换对象为字符串。JSON.parse 转换 JSON 字符串为对象。组合它们可以转换对象为字符串,然后再反向过程会创建一个新数据结构。

注意:这是一个安全复制多维对象/数组的方法。

const nestedNumbers = [[1], [2]];
const numbersCopy = JSON.parse(JSON.stringify(nestedNumbers));

numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);

// [[1], [2]]
// [[1, 300], [2]]
// 这两个数组是完全不同的

Array.concat(浅拷贝)

Array.cancat 使用值或其它数组组合数组。

[1, 2, 3].concat(4); // [1, 2, 3, 4]
[1, 2, 3].concat([4, 5]); // [1, 2, 3, 4, 5]

如果不传参或是传一个空数组,将返回一个浅拷贝的数组。

[1, 2, 3].concat(); // [1, 2, 3]
[1, 2, 3].concat([]); // [1, 2, 3]

注意:这同样是通过引用而不是值分配对象/数组。多维数组的克隆复制不安全。

Array.from(浅拷贝)

Array.from 可以转换可迭代对象和类数组对象为数组。

const numbers = [1, 2, 3];
const numbersCopy = Array.from(numbers);
// [1, 2, 3]

注意:这同样是通过引用而不是值分配对象/数组。多维数组的克隆复制不安全。