浅copy
"拷贝"是指将一个对象的值复制到另一个对象中。 由于javascript基本数据类型存在栈内存中,所以对于基本数据类型来说,一定是深copy。 以下探究的浅拷贝和深拷贝只是只针对引用类型,
1、浅拷贝
浅拷贝仅复制对象的第一层属性,对于嵌套的引用数据类型,它只复制引用地址,而**不复制实际内容
特点
- 基本数据类型的值会被完全复制。
- 引用数据类型的值只会复制引用(指针),不会复制实际的内存内容。
实现方式
- 使用
Object.assign():
const obj1 = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj1);
shallowCopy.b.c = 42; // 修改拷贝对象
console.log(obj1.b.c); // 42,原对象也被影响
- 使用
展开运算符(...)
const obj1 = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj1 };
shallowCopy.b.c = 42;
console.log(obj1.b.c); // 42
2、深拷贝
深拷贝会递归复制对象的所有层级属性,包括嵌套的引用数据类型,生成一个与原对象完全独立的副本。
特点:
- 原对象和拷贝对象完全独立,互不影响。
- 需要处理多层嵌套的对象和数组。
实现方式一:使用JSON.parse(JSON.stringify()) :
工作原理:通过JSON.stringify()将JavaScript对象转换成JSON字符串,再通过JSON.parse()将JSON字符串转换回JavaScript对象,实现深度拷贝。
const obj1 = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj1));
deepCopy.b.c = 42;
console.log(obj1.b.c); // 2,原对象未受影响
缺点: 无法处理函数、循环引用、特殊对象(如Date、RegExp、undefined等),且会丢失对象的原型链
实现方式二:使用structuredClone :
structuredClone 是现代浏览器提供的原生方法,支持多种数据类型的深拷贝
const obj = {
num: 42,
str: "hello",
func: function (x) { return x * 2; },
date: new Date(),
nested: { arr: [1, 2, 3] },
map: new Map([["key", "value"]]),
set: new Set([1, 2, 3]),
};
const clonedObj = structuredClone(obj);
console.log(clonedObj);
console.log(clonedObj.date === obj.date); // false
console.log(clonedObj.map === obj.map); // false
优点:原生支持、性能好、支持 Date、Map、Set 等常见对象
缺点:不支持函数、兼容性受限(需在现代浏览器或 Node.js 17+ 环境)
实现方式三:基于 lodash 的 cloneDeep
lodash 的 cloneDeep 是一个成熟的深拷贝工具,适合复杂对象的复制。
import _ from 'lodash';
const obj = {
num: 42,
str: "hello",
func: function (x) { return x * 2; },
date: new Date(),
nested: { arr: [1, 2, 3] },
map: new Map([["key", "value"]]),
set: new Set([1, 2, 3]),
};
const clonedObj = _.cloneDeep(obj);
console.log(clonedObj);
console.log(clonedObj.func === obj.func); // true,函数只拷贝引用
console.log(clonedObj.date === obj.date); // false
优点:支持几乎所有常见类型的深拷贝,社区广泛使用,经过充分测试
缺点:需要引入第三方库、函数仍然是引用拷贝
实现方式四:递归遍历
function deepCopy(value, hash = new WeakMap()) {
// 检查是否是原始类型(原始类型直接返回)
if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return value;
if (hash.has(value)) return hash.get(value); // 处理循环引用
let clone;
if(value instanceof Date){
clone = new Date(value);
}else if(value instanceof RegExp){
clone = new RegExp(value.source, value.flags)
}else if(value instanceof Map){
clone = new Map();
hash.set(value, clone);
for(const [key, val] of value){
clone.set(deepCopy(key, hash), deepCopy(val, hash));
}
}else if(value instanceof Set){
clone = new Set();
hash.set(value, clone);
for(const val of value){
clone.add(deepCopy(val, hash));
}
} else if(typeof value === 'function'){
clone = new Function( value.toString())()
}else{
clone = Array.isArray(value) ? [] : {};
hash.set(value, clone); // 将当前对象存入 WeakMap,防止循环引用
// 遍历对象或数组并递归拷贝
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
clone[key] = deepCopy(value[key], hash);
}
}
// 拷贝对象的Symbol键
const symbols = Object.getOwnPropertySymbols(value);
for(let sym of symbols){
clone[sym] = deepCopy(value[sym], hash)
}
}
return clone;
}
const symbol1 = Symbol('a');
// 示例用法
const obj = {
a: "Alice",
b: 25,
c: ["reading", "traveling"],
e: {
a: 1,
b: [1, 2, 3],
},
f: new Date(),
g: /abc/g,
h: new Map(),
i: new Set(),
[symbol1]: {a:3},
k: (x) => x * 2,
};
const copiedObj = deepCopy(obj);
console.log(copiedObj);
console.log(copiedObj.e === obj.e); // false
console.log(copiedObj.f === obj.f); // false
console.log(copiedObj.e.b === obj.e.b); // false
console.log(copiedObj[symbol1] === obj[symbol1]); // false
console.log(copiedObj.k === obj.k); // false