用 JavaScript 处理很多事情,都有很多不同的方法。 我写过这篇《用 JavaScript 编写 pipe/compose 的 10 种方法》,现在我们看看数组的实现。
1、JavaScript 展开操作符(浅拷贝)
自从 ES6 发布以来,这一直是最受欢迎的方法。这是一个简短的语法,在使用 React 和 Redux 之类的库时,你会发现它非常有用。
numbers = [1, 2, 3];
numbersCopy = [...numbers];
注意:这不能安全地复制多维数组。数组/对象值是按 引用 而不是按 值 复制的。
这个写法很好:
numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// numbers is left alone
这么写不太好:
nestedNumbers = [[1], [2]];
numbersCopy = [...nestedNumbers];
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// They've both been changed because they share references
2、for() 循环(浅拷贝)
考虑到函数式编程在业界的流行程度,我认为这种方法 最不受欢迎。
numbers = [1, 2, 3];
numbersCopy = [];
for (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 is left alone
这么写不太好:
nestedNumbers = [[1], [2]];
numbersCopy = [];
for (i = 0; i < nestedNumbers.length; i++) {
numbersCopy[i] = nestedNumbers[i];
}
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// They've both been changed because they share references
3、while() 循环(浅拷贝)
和 for
循环类似——不太简洁,不够描述型......但是它总归也能用!
numbers = [1, 2, 3];
numbersCopy = [];
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 is left alone
这么写不太好:
nestedNumbers = [[1], [2]];
numbersCopy = [];
i = -1;
while (++i < nestedNumbers.length) {
numbersCopy[i] = nestedNumbers[i];
}
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// They've both been changed because they share references
4、Array.map(浅拷贝)
回到现代领域,我们会发现 map
函数。映射起源于数学,map
是在保留结构的同时将集合转换为另一种类型的集合的概念。
这意味着 Array.map
每次都会返回相同长度的数组。
要将数字列表加倍,请使用带有 double
函数的 map
。
numbers = [1, 2, 3];
double = (x) => x * 2;
numbers.map(double);
拷贝呢?
没错,本文是关于拷贝数组的。要复制数组,只需在 map
调用中返回元素即可。
numbers = [1, 2, 3];
numbersCopy = numbers.map((x) => x);
如果你想更加数学化,(x) => x
被称为恒等,它返回给定的任何参数。
map(identity)
拷贝一个列表。
identity = (x) => x;
numbers.map(identity);
// [1, 2, 3]
注意:这也是通过 引用 而不是 值 来分配对象/数组。
5、Array.filter(浅拷贝)
此函数会返回一个数组,就像 map
一样,但是不能保证长度相同。
如果你要过滤偶数,怎么办?
[1, 2, 3].filter((x) => x % 2 === 0);
// [2]
输入数组的长度为 3,但结果长度为 1。
但是,如果 filter
始终返回 true
,则将获得重复项!
numbers = [1, 2, 3];
numbersCopy = numbers.filter(() => true);
每个元素都通过测试,因此会返回。
注意:这也是通过 引用 而不是 值 来分配对象/数组。
6、Array.reduce(浅拷贝)
我觉得使用 reduce
拷贝数组很糟糕,因为它的功能远不止于此。但是,我们试试看......
numbers = [1, 2, 3];
numbersCopy = numbers.reduce((newArray, element) => {
newArray.push(element);
return newArray;
}, []);
当 reduce
循环遍历列表时,转换初始值。
这里的初始值是一个空数组,我们将使用每个元素填充它。该数组必须从函数中返回,以在下一次迭代中使用。
注意:这也是通过 引用 而不是 值 来分配对象/数组。
7、Array.slice(浅拷贝)
slice
根据你提供的开始/结束索引返回数组的浅拷贝副本。
如果我们想得到前 3 个元素:
[1, 2, 3, 4, 5].slice(0, 3);
// [1, 2, 3]
// Starts at index 0, stops at index 3
如果我们想得到所有元素,不提供任何参数:
numbers = [1, 2, 3, 4, 5];
numbersCopy = numbers.slice();
// [1, 2, 3, 4, 5]
注意:这是一个 浅拷贝,因此它也按 引用 而不是按 值 分配对象/数组。
8、JSON.parse 和 JSON.stringify(深拷贝)
JSON.stringify
将一个对象转换成一个字符串。
JSON.parse
将一个字符串转换成一个对象。
将它们组合在一起可以将一个对象变成一个字符串,然后反过来可以创建一个全新的数据结构。
注意:这个方法可以安全地复制深度嵌套的对象/数组!
nestedNumbers = [[1], [2]];
numbersCopy = JSON.parse(JSON.stringify(nestedNumbers));
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1], [2]]
// [[1, 300], [2]]
// These two arrays are completely separate!
9、Array.concat(浅拷贝)
concat
将数组与值或其他数组组合。
[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]
注意:这也是通过 引用 而不是 值 来分配对象/数组。
10、Array.from(浅拷贝)
这可以将任何可迭代对象转换为数组。输入数组将返回浅拷贝。
numbers = [1, 2, 3];
numbersCopy = Array.from(numbers);
// [1, 2, 3]
注意:这也是通过 引用 而不是 值 来分配对象/数组。
总结
怎么样,有意思吧?
我尝试仅用一个步骤进行拷贝。如果你采用多种方法和技术,你将会发现更多。