你以为JSON.parse(JSON.stringify())就是深度克隆的全部?大错特错!这5种方案让你彻底告别克隆焦虑!
方案一:JSON方法(最常用但问题最多)
function cloneByJSON(obj) {
return JSON.parse(JSON.stringify(obj));
}
// 优点:简单粗暴,一行代码
// 缺点:丢失函数、循环引用报错、Date变字符串等
方案二:递归克隆(本文核心方案)
function deepClone(obj, visited = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (visited.has(obj)) return visited.get(obj);
// 处理各种内置对象
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
if (obj instanceof Map) {
const cloned = new Map();
visited.set(obj, cloned);
obj.forEach((v, k) => cloned.set(deepClone(k, visited), deepClone(v, visited)));
return cloned;
}
if (obj instanceof Set) {
const cloned = new Set();
visited.set(obj, cloned);
obj.forEach(v => cloned.add(deepClone(v, visited)));
return cloned;
}
const cloned = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
visited.set(obj, cloned);
// 复制所有属性
const descriptors = Object.getOwnPropertyDescriptors(obj);
for (const [key, descriptor] of Object.entries(descriptors)) {
if (descriptor.value) descriptor.value = deepClone(descriptor.value, visited);
Object.defineProperty(cloned, key, descriptor);
}
return cloned;
}
方案三:结构化克隆API(现代浏览器方案)
// 使用浏览器的结构化克隆API
function cloneByStructuredClone(obj) {
return structuredClone(obj);
}
// 或者在Node.js环境中
function cloneByMessageChannel(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = (ev) => resolve(ev.data);
port1.postMessage(obj);
});
}
// 同步版本
function cloneByMessageChannelSync(obj) {
const { port1, port2 } = new MessageChannel();
let result;
port2.onmessage = (ev) => result = ev.data;
port1.postMessage(obj);
port2.start();
// 注意:这实际上不是真正的同步,需要微任务处理
return result;
}
方案四:lodash的_.cloneDeep(第三方库方案)
import _ from 'lodash';
function cloneByLodash(obj) {
return _.cloneDeep(obj);
}
// 或者只引入cloneDeep
import cloneDeep from 'lodash/cloneDeep';
// 自定义版本的lodash风格克隆
function lodashStyleClone(obj, customizer) {
if (typeof customizer === 'function') {
const result = customizer(obj);
if (result !== undefined) return result;
}
// 简化版的lodash逻辑
if (typeof obj !== 'object' || obj === null) return obj;
const isArr = Array.isArray(obj);
const result = isArr ? [] : {};
if (isArr) {
for (let i = 0; i < obj.length; i++) {
result[i] = lodashStyleClone(obj[i], customizer);
}
} else {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = lodashStyleClone(obj[key], customizer);
}
}
}
return result;
}
方案五:Proxy代理克隆(响应式克隆)
function cloneByProxy(obj, visited = new WeakMap()) {
if (visited.has(obj)) return visited.get(obj);
if (obj === null || typeof obj !== 'object') return obj;
const handlers = {
get(target, prop) {
return cloneByProxy(Reflect.get(target, prop), visited);
},
set(target, prop, value) {
return Reflect.set(target, prop, cloneByProxy(value, visited));
}
};
const cloned = Array.isArray(obj) ? [] : {};
visited.set(obj, cloned);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = cloneByProxy(obj[key], visited);
}
}
return new Proxy(cloned, handlers);
}
完整对比测试
// 测试数据
const testData = {
date: new Date(),
regex: /test/gi,
func: () => console.log('test'),
undefinedVal: undefined,
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
array: [1, 2, { nested: true }],
circular: null
};
testData.circular = testData;
// 测试函数
function testCloneMethod(method, name, data) {
console.time(name);
try {
const result = method(data);
console.log(`${name}:`, {
成功: true,
Date类型: result.date instanceof Date,
RegExp类型: result.regex instanceof RegExp,
函数保持: typeof result.func === 'function',
undefined保持: result.undefinedVal === undefined,
循环引用: result.circular === result,
Map保持: result.map instanceof Map,
Set保持: result.set instanceof Set
});
} catch (error) {
console.log(`${name}:`, { 成功: false, 错误: error.message });
}
console.timeEnd(name);
}
// 执行测试
testCloneMethod(cloneByJSON, 'JSON方法', testData);
testCloneMethod(deepClone, '递归克隆', testData);
testCloneMethod(structuredClone, '结构化克隆', testData);
testCloneMethod(cloneByLodash, 'Lodash', testData);
testCloneMethod(cloneByProxy, 'Proxy克隆', testData);
性能对比结果
| 方案 | 速度 | 完整性 | 浏览器支持 | 内存使用 |
|---|---|---|---|---|
| JSON方法 | ⚡⚡⚡⚡⚡ | ❌ | 全支持 | 低 |
| 递归克隆 | ⚡⚡⚡ | ✅ | 全支持 | 中 |
| 结构化克隆 | ⚡⚡⚡⚡ | ✅ | 现代浏览器 | 中 |
| Lodash | ⚡⚡ | ✅ | 全支持 | 中 |
| Proxy克隆 | ⚡ | ✅ | 全支持 | 高 |
场景化选择指南
1. 简单数据克隆
// 使用JSON方法(最快)
const simpleData = { a: 1, b: 'string', c: true };
const cloned = JSON.parse(JSON.stringify(simpleData));
2. 复杂对象克隆
// 使用递归克隆(最安全)
const complexData = {
date: new Date(),
func: () => {},
map: new Map()
};
const cloned = deepClone(complexData);
3. 现代浏览器环境
// 使用结构化克隆(原生支持)
if (typeof structuredClone === 'function') {
const cloned = structuredClone(data);
} else {
const cloned = deepClone(data);
}
4. 已有lodash的项目
// 直接使用lodash
import { cloneDeep } from 'lodash';
const cloned = cloneDeep(data);
5. 需要响应式克隆
// 使用Proxy方案
const reactiveClone = cloneByProxy(data);
reactiveClone.nested.value = 'changed'; // 自动深度克隆
高级技巧:混合方案
function smartClone(obj, options = {}) {
const {
useStructuredClone = typeof structuredClone === 'function',
fallbackToCustom = true,
maxDepth = 20
} = options;
// 深度检测
function getDepth(o, currentDepth = 0, visited = new WeakSet()) {
if (currentDepth > maxDepth || visited.has(o)) return currentDepth;
if (o === null || typeof o !== 'object') return currentDepth;
visited.add(o);
let maxChildDepth = currentDepth;
if (Array.isArray(o)) {
for (const item of o) {
maxChildDepth = Math.max(maxChildDepth, getDepth(item, currentDepth + 1, visited));
}
} else {
for (const key in o) {
if (o.hasOwnProperty(key)) {
maxChildDepth = Math.max(maxChildDepth, getDepth(o[key], currentDepth + 1, visited));
}
}
}
return maxChildDepth;
}
const depth = getDepth(obj);
// 根据深度选择策略
if (depth <= 2) {
// 浅层对象使用扩展运算符
return Array.isArray(obj) ? [...obj] : { ...obj };
} else if (useStructuredClone && depth <= 10) {
// 中等深度使用结构化克隆
return structuredClone(obj);
} else {
// 深度对象使用自定义递归
return deepClone(obj);
}
}
避坑总结
- 不要盲目使用JSON方法 - 它丢失的信息比你想象的要多
- 循环引用必须处理 - 否则会导致栈溢出或内存泄漏
- 考虑性能边界 - 超大数据量需要特殊处理
- 浏览器兼容性 - 结构化克隆只在现代浏览器中可用
- 内存使用 - 深度克隆会创建大量新对象
最终建议
- 🎯 日常开发:使用递归克隆方案
- 🚀 性能敏感:先检测深度,选择合适策略
- 📦 已有lodash:直接使用_.cloneDeep
- 🌐 现代项目:优先使用structuredClone
- 🔧 特殊需求:根据场景定制克隆策略
选择正确的克隆方案,让你的代码既安全又高效!不要再被简单的JSON方法限制住了,根据实际需求选择最合适的工具吧!