Copy array by value

Copy array by value

技术背景

在 JavaScript 中,数组是引用类型,直接使用赋值操作符(=)复制数组,实际上只是复制了数组的引用,而不是数组的值。这意味着修改新数组会影响原数组,反之亦然。因此,需要使用特定的方法来实现数组按值复制,确保新数组和原数组相互独立。

实现步骤

1. 确定数组元素类型

数组元素可分为三种类型:

  • 字面量值(Literal values):如布尔值、数字、字符串。
  • 字面量结构(Literal structures):如数组、对象。
  • 原型对象(Prototypes):如 new Boolean()new Number() 等创建的对象。

2. 根据元素类型选择复制方法

2.1 仅包含字面量值的数组

可使用以下方法:

  • slice()let newArray = oldArray.slice();
  • 扩展运算符(Spread operator):let newArray = [...oldArray];
  • splice(0)let newArray = oldArray.splice(0);
  • concat()let newArray = oldArray.concat();

2.2 包含字面量值和字面量结构的数组

可使用 JSON.parse(JSON.stringify(myArray)) 方法,但该方法无法处理原型对象和函数。

2.3 包含所有类型元素的数组

  • structuredClone()let newArray = structuredClone(oldArray);
  • 使用第三方库,如 Lodash 的 cloneDeep() 或 jQuery 的 extend(true, [], myArray)
  • 自定义递归函数:
1
2
3
4
5
6
7
8
9
function copy(aObject) {
let bObject = Array.isArray(aObject) ? [] : {};
let value;
for (const key in aObject) {
value = aObject[key];
bObject[key] = (typeof value === "object") ? copy(value) : value;
}
return bObject;
}

核心代码

浅拷贝示例

1
2
3
4
// 浅拷贝
let oldArray = [1, 2, 3, 4, 5];
let newArray = oldArray.slice();
console.log({ newArray });

深拷贝示例

1
2
3
4
5
6
// 深拷贝
let arr1 = [1, 2, [1, 2, 3]];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[2][0] = 5;
console.log(arr1); // [1, 2, [1, 2, 3]]
console.log(arr2); // [1, 2, [5, 2, 3]]

最佳实践

  • 性能考虑:对于简单的数组复制,for 循环性能最高,但代码较冗长;.slice() 方法性能也不错,且代码简洁,适合日常使用。避免在不需要深拷贝且对性能有要求的场景使用 JSON.parse(JSON.stringify(arr)),因为该方法有较大的开销。
  • 兼容性考虑:使用 structuredClone() 方法时,需注意浏览器兼容性;Array.from() 方法在旧浏览器中支持不佳,使用前需检查支持情况。

常见问题

  • 浅拷贝问题:使用 slice()concat() 等方法进行浅拷贝时,对于数组中的对象或嵌套数组,只是复制了引用,修改新数组中的对象或嵌套数组会影响原数组。解决方法是使用深拷贝方法。
  • JSON.parse(JSON.stringify()) 的局限性:该方法无法处理函数、正则表达式、日期对象等特殊对象,会丢失这些对象的方法和属性。
  • 兼容性问题:一些新的方法和特性(如 structuredClone()、扩展运算符)在旧浏览器中可能不支持,需考虑兼容性或使用转译工具。