在JavaScript中,浅拷贝(Shallow Copy)是指创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型(如对象、数组),拷贝的就是内存地址,因此如果其中一个对象改变了这个地址的内容,就会影响到另一个对象。以下是JavaScript中常用的浅拷贝方法:
1. Object.assign()
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。这是实现浅拷贝的一个非常简便的方法。
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
copy.a = 10; // 不会影响到 original.a
copy.b.c = 20; // 会影响到 original.b.c,因为b是引用类型
2. 展开运算符(Spread Operator)
展开运算符...
可以在函数调用/数组构造时,或在解构赋值语句中,将数组表达式或对象表达式展开。这也可以用于对象的浅拷贝。
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
copy.a = 10; // 不会影响到 original.a
copy.b.c = 20; // 会影响到 original.b.c,因为b是引用类型
3. Array.prototype.slice()(针对数组)
slice()
方法返回一个新的数组对象,这一对象是一个由 begin
到 end
(不包括 end
)的浅拷贝的原数组的片段。原数组不会被修改。
const original = [1, 2, { c: 3 }];
const copy = original.slice();
copy[0] = 10; // 不会影响到 original[0]
copy[2].c = 30; // 会影响到 original[2].c,因为[2]是引用类型
4. Array.prototype.concat()(针对数组)
concat()
方法用于合并两个或多个数组。此方法不会改变现有的数组,而是返回一个新数组。
const original = [1, 2, { c: 3 }];
const copy = original.concat();
copy[0] = 10; // 不会影响到 original[0]
copy[2].c = 30; // 会影响到 original[2].c,因为[2]是引用类型
对于浅拷贝,如果原对象中的属性值是引用类型(如对象或数组),那么拷贝得到的新对象中的这个属性值将和原对象中的这个属性值指向同一个内存地址。因此,对这个属性值进行非替换性修改(如修改对象的属性或数组的元素)会同时影响到原对象和新对象。
深拷贝
在JavaScript中,实现深拷贝(Deep Copy)通常意味着创建一个新对象,该对象有着原始对象属性值的一份完全独立的拷贝,包括嵌套的对象和数组。深拷贝确保原始对象和拷贝对象之间没有任何共享的内存地址,因此修改拷贝对象不会影响到原始对象。以下是JavaScript中常用的一些深拷贝方法:
1. 使用 JSON.parse(JSON.stringify())
这是实现深拷贝的一种简单方法,但它有局限性,比如无法处理函数、undefined
、Symbol
、循环引用以及Date
、RegExp
等特殊对象。
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
// 示例
const original = { a: 1, b: { c: 2 }, d: function() {} };
const copy = deepCopy(original);
copy.b.c = 3; // 不会影响到 original.b.c
console.log(copy.d); // undefined,因为函数无法被JSON序列化
2. 手动实现递归拷贝
为了克服JSON.parse(JSON.stringify())
的局限性,可以手动实现一个递归函数来进行深拷贝。
function deepCopy(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
if (hash.has(obj)) return hash.get(obj);
let cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepCopy(obj[key], hash);
}
}
return cloneObj;
}
// 注意:这个实现没有处理Map、Set、Blob、File、ArrayBuffer等类型
3. 使用第三方库
许多JavaScript库提供了深拷贝的实现,如lodash
的_.cloneDeep
方法。
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const copy = _.cloneDeep(original);
copy.b.c = 3; // 不会影响到 original.b.c
4. 处理特殊类型(如Map、Set、ArrayBuffer等)
对于特殊类型,如Map
、Set
、ArrayBuffer
等,你需要在递归拷贝函数中添加额外的逻辑来处理它们。
function deepCopy(obj, hash = new WeakMap()) {
// ...(处理基本类型和已知对象类型的逻辑)
if (obj instanceof Map) {
let clone = new Map();
hash.set(obj, clone);
obj.forEach((value, key) => {
clone.set(deepCopy(key, hash), deepCopy(value, hash));
});
return clone;
}
if (obj instanceof Set) {
let clone = new Set();
hash.set(obj, clone);
obj.forEach(value => {
clone.add(deepCopy(value, hash));
});
return clone;
}
// 处理ArrayBuffer、TypedArray等...
// 默认处理对象
let cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// ...(遍历属性并递归拷贝的逻辑)
return cloneObj;
}
请注意,深拷贝可能会变得相当复杂,特别是当处理包含循环引用的对象时。在这种情况下,使用WeakMap
(如上例所示)或类似的数据结构来跟踪已经拷贝过的对象是非常有用的,以避免无限循环。