JavaScript 对象克隆技术详解:从表层复制到深度递归
克隆机制的本质差异
在 JavaScript 中,数据复制分为两个层级。表层克隆仅复制对象的直接属性,若属性值为引用类型(如对象、数组),新旧对象将共享同一内存地址。而深度克隆会递归遍历所有层级,创建完全独立的数据副本,新旧对象之间不存在任何引用关联。
表层克隆的实现方案
方案一:传统迭代法
const source = { x: 10, y: 20 };
function shallowCopy(target) {
const result = {};
for (const prop in target) {
if (target.hasOwnProperty(prop)) {
result[prop] = target[prop];
}
}
return result;
}
console.log(shallowCopy(source)); // { x: 10, y: 20 }
方案二:键名遍历法
const source = { x: 10, y: 20 };
function shallowCopy(target) {
const result = {};
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
result[key] = target[key];
}
return result;
}
方案三:解构赋值法
const source = { x: 10, y: 20 };
const copy = { ...source };
copy.x = 99;
console.log(source.x); // 10(未改变)
方案四:Object.assign 方法
const source = { x: 10, y: 20 };
const copy = Object.assign({}, source);
方案五:Lodash 工具库
const _ = require('lodash');
const source = { x: 10, y: 20 };
const copy = _.clone(source);
深度克隆的实现方案
方案一:序列化反序列化法
利用 JSON 的序列化特性实现快速深拷贝,但存在明显局限:无法处理函数、undefined、Symbol、循环引用及特殊对象(如 Date、RegExp、Map、Set)。
const source = {
name: '示例',
nested: { value: 42 },
items: [1, 2, 3]
};
const deepCopy = JSON.parse(JSON.stringify(source));
方案二:递归遍历法
function deepClone(source, hash = new WeakMap()) {
// 处理 null 或基本类型
if (source === null || typeof source !== 'object') {
return source;
}
// 处理循环引用
if (hash.has(source)) {
return hash.get(source);
}
// 处理日期对象
if (source instanceof Date) {
return new Date(source.getTime());
}
// 处理正则对象
if (source instanceof RegExp) {
return new RegExp(source.source, source.flags);
}
// 创建新容器
const result = Array.isArray(source) ? [] : {};
hash.set(source, result);
// 递归复制属性
const keys = Reflect.ownKeys(source);
for (const key of keys) {
result[key] = deepClone(source[key], hash);
}
return result;
}
// 测试用例
const original = {
id: 1,
meta: { created: new Date(), pattern: /test/g },
tags: ['a', 'b'],
self: null
};
original.self = original; // 循环引用
const cloned = deepClone(original);
console.log(cloned.meta.created !== original.meta.created); // true
console.log(cloned.self === cloned); // true(循环引用正确指向自身)
方案三:Lodash 深度克隆
const _ = require('lodash');
const source = {
data: { value: 100 },
list: [{ id: 1 }, { id: 2 }]
};
const cloned = _.cloneDeep(source);
console.log(cloned.data === source.data); // false
方案四:结构化克隆算法(现代环境)
// 浏览器环境:使用 MessageChannel
async function structuredClone(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = (ev) => resolve(ev.data);
port1.postMessage(obj);
});
}
// Node.js 环境:v17.0.0+ 内置支持
const cloned = structuredClone(source);
技术选型建议
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 简单对象,无嵌套 | 扩展运算符 {...obj} |
语法简洁,性能最优 |
| 需要合并多个对象 | Object.assign() |
支持多源对象合并 |
| 纯数据对象深拷贝 | JSON.parse(JSON.stringify()) |
实现简单,注意类型限制 |
| 复杂对象,含特殊类型 | 递归实现或 _.cloneDeep |
完整支持各类数据类型 |
| 现代浏览器/Node.js | structuredClone() |
原生支持,性能优异 |