JavaScript 深拷贝与浅拷贝完全指南:JSON、递归与结构化克隆
Introduction
JavaScript 中对象和数组是引用类型,直接赋值只是复制了引用地址,而非实际数据。理解浅拷贝与深拷贝的区别,以及如何正确实现深拷贝,是避免数据意外污染的前提。本文对比5种拷贝方案及其适用场景。
浅拷贝 vs 深拷贝
/* 浅拷贝:只复制第一层,深层仍是引用 */
var original = { a: 1, b: { c: 2 } };
var shallow = Object.assign({}, original);
shallow.b.c = 999;
console.log(original.b.c); // 999(被意外修改!)
/* 深拷贝:所有层级都复制,独立互不影响 */
var deep = JSON.parse(JSON.stringify(original));
deep.b.c = 999;
console.log(original.b.c); // 2(不受影响)*/
方法1:JSON.parse + JSON.stringify(简单场景)
var deep = JSON.parse(JSON.stringify(original));
/* 优点:语法简单 */
/* 缺点:
- undefined、函数、Symbol 会被忽略
- Date 对象变成字符串
- RegExp、Error 对象变成空对象
- NaN、Infinity 变成 null
- 循环引用会报错
*/
方法2:structuredClone(现代方法,浏览器内置)
var deep = structuredClone(original);
/* 优点:
- 支持 undefined、函数、Symbol
- 支持循环引用(不报错)
- 支持大部分内置类型
- structuredClone({ date: new Date() }) 会保留 Date 对象
*/
/* 缺点:不能克隆函数(Functions cannot be structured-cloned)*/
方法3:手写递归深拷贝(完全可控)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(function(item) { return deepClone(item); });
}
var cloned = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
方法4:lodash 的 cloneDeep
/* 如果项目有 lodash 依赖,可以使用 */
var deep = _.cloneDeep(original);
/* _.cloneDeep 处理了循环引用、函数、Symbol、正则等边界情况 */
常见问题
- 循环引用:JSON.stringify 会报错,structuredClone 支持,lodash.cloneDeep 支持,手写递归需要额外处理(用 WeakMap 记录已拷贝的对象)。
- 函数拷贝:JSON 和 structuredClone 都无法拷贝函数,只能手写递归保留函数引用。
- Vue 中的深拷贝:Vue.set() 是响应式写入,不是拷贝。深拷贝后替换整个响应式对象才是正确的深拷贝方式。
- MDN structuredClone:MDN structuredClone
- lodash.cloneDeep 源码分析