在JavaScript中,对象、数组等引用类型的变量存储的是内存地址的引用,直接通过赋值操作符得到的只是地址的复制,修改新变量会同步影响原变量。深浅拷贝就是为了解决引用类型复制时的数据隔离问题而出现的两种方案,二者的核心差异在于是否复制引用类型内部的嵌套引用类型数据。

浅拷贝的实现原理
浅拷贝只会复制目标对象的第一层属性,如果属性是基本类型,就直接复制值;如果属性是引用类型,就复制其引用地址,因此修改新对象的引用类型属性仍然会影响原对象。
常见浅拷贝实现方式
1. 使用Object.assign方法
Object.assign是ES6提供的对象合并方法,当只传入一个目标对象时,会返回该对象的浅拷贝结果。
// 原对象
const originObj = {
name: 'test',
info: {
age: 20,
hobby: ['reading', 'coding']
}
};
// 浅拷贝
const shallowCopyObj = Object.assign({}, originObj);
// 修改基本类型属性,不影响原对象
shallowCopyObj.name = 'newTest';
console.log(originObj.name); // 输出test
// 修改引用类型属性,会影响原对象
shallowCopyObj.info.age = 25;
console.log(originObj.info.age); // 输出25
2. 扩展运算符实现浅拷贝
扩展运算符...也可以快速实现对象的浅拷贝,原理和Object.assign一致。
const originObj = {
name: 'test',
list: [1, 2, 3]
};
// 扩展运算符浅拷贝
const shallowCopyObj = { ...originObj };
shallowCopyObj.list.push(4);
console.log(originObj.list); // 输出[1,2,3,4]
3. 手写浅拷贝函数
手写浅拷贝的核心逻辑是遍历原对象的所有可枚举属性,将其赋值到新对象中。
function shallowClone(target) {
// 只处理对象类型
if (typeof target !== 'object' || target === null) {
return target;
}
// 根据原对象类型初始化新对象
const result = Array.isArray(target) ? [] : {};
// 遍历属性赋值
for (let key in target) {
// 只复制自身属性,不复制原型链上的属性
if (target.hasOwnProperty(key)) {
result[key] = target[key];
}
}
return result;
}
// 测试
const origin = { a: 1, b: { c: 2 } };
const copy = shallowClone(origin);
copy.b.c = 3;
console.log(origin.b.c); // 输出3
深拷贝的实现原理
深拷贝会递归复制目标对象的所有层级属性,无论是基本类型还是引用类型,都会复制出一份完全独立的新数据,修改新对象不会影响原对象。
常见深拷贝实现方式
1. JSON序列化反序列化法
利用JSON.stringify将对象转为JSON字符串,再用JSON.parse将字符串转回对象,这个过程会重新构建所有层级的数据,实现深拷贝。
const originObj = {
name: 'test',
info: {
age: 20,
hobby: ['reading', 'coding']
},
date: new Date(),
func: function() { console.log('test'); }
};
// JSON方法深拷贝
const deepCopyObj = JSON.parse(JSON.stringify(originObj));
deepCopyObj.info.age = 25;
console.log(originObj.info.age); // 输出20
// 注意:该方法有局限性,会丢失函数、undefined、Date等特殊类型
console.log(deepCopyObj.date); // 输出字符串格式的日期,不是Date对象
console.log(deepCopyObj.func); // 输出undefined
这种方式的局限性很明显:无法复制函数、undefined、Symbol类型,Date对象会变成字符串,RegExp、Error等对象会丢失原有特性,也不能处理循环引用的情况。
2. 手写递归深拷贝函数
递归深拷贝的核心是遍历对象的所有属性,如果遇到引用类型属性,就递归调用拷贝函数,直到所有属性都是基本类型为止。
function deepClone(target, map = new WeakMap()) {
// 处理基本类型和null
if (typeof target !== 'object' || target === null) {
return target;
}
// 处理循环引用,如果已经拷贝过该对象,直接返回缓存的结果
if (map.has(target)) {
return map.get(target);
}
// 处理特殊对象类型
if (target instanceof Date) {
return new Date(target);
}
if (target instanceof RegExp) {
return new RegExp(target.source, target.flags);
}
// 初始化结果对象,保持和原对象一致的类型
const result = Array.isArray(target) ? [] : {};
// 将当前对象存入缓存
map.set(target, result);
// 遍历所有自有属性(包括Symbol属性)
Reflect.ownKeys(target).forEach(key => {
// 递归拷贝属性值
result[key] = deepClone(target[key], map);
});
return result;
}
// 测试
const origin = {
name: 'test',
info: { age: 20 },
date: new Date(),
func: function() { return 1; },
sym: Symbol('test')
};
// 添加循环引用
origin.self = origin;
const copy = deepClone(origin);
copy.info.age = 25;
console.log(origin.info.age); // 输出20
console.log(copy.date instanceof Date); // 输出true
console.log(copy.func()); // 输出1
console.log(copy.self === copy); // 输出true,循环引用处理正常
深浅拷贝的对比与选择
我们可以通过下表快速对比两种拷贝方式的差异:
| 对比维度 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制层级 | 仅第一层属性 | 所有层级属性 |
| 数据独立性 | 嵌套引用类型仍共享 | 完全独立 |
| 性能消耗 | 低,速度快 | 高,速度慢 |
| 适用场景 | 对象只有一层属性,或明确不需要隔离嵌套引用类型 | 需要完全隔离原对象和新对象,嵌套层级多或不确定 |
在实际开发中,如果对象结构简单,只有一层基本类型属性,优先选择浅拷贝,性能更好;如果对象包含多层嵌套的引用类型,或者需要避免修改新对象影响原数据,就选择深拷贝。如果深拷贝的对象不包含特殊类型,也可以使用JSON序列化方法快速实现,否则建议使用递归深拷贝函数。
注意事项
- 基本类型(number、string、boolean、null、undefined、Symbol)不存在拷贝问题,赋值就是值的复制。
- 浅拷贝的实现方式还有数组的
slice、concat方法,原理和对象浅拷贝一致。 - 手写深拷贝函数时,一定要处理循环引用的情况,否则会出现栈溢出错误。
- 如果项目中频繁使用深拷贝,可以考虑引入成熟的工具库如lodash的
cloneDeep方法,避免重复造轮子。
JavaScript深浅拷贝对象操作引用类型修改时间:2026-06-11 18:33:22