🚀深度克隆的5种方案大比拼!90%的开发者只知道第一种

112 阅读4分钟

你以为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);
  }
}

避坑总结

  1. 不要盲目使用JSON方法 - 它丢失的信息比你想象的要多
  2. 循环引用必须处理 - 否则会导致栈溢出或内存泄漏
  3. 考虑性能边界 - 超大数据量需要特殊处理
  4. 浏览器兼容性 - 结构化克隆只在现代浏览器中可用
  5. 内存使用 - 深度克隆会创建大量新对象

最终建议

  • 🎯 日常开发:使用递归克隆方案
  • 🚀 性能敏感:先检测深度,选择合适策略
  • 📦 已有lodash:直接使用_.cloneDeep
  • 🌐 现代项目:优先使用structuredClone
  • 🔧 特殊需求:根据场景定制克隆策略

选择正确的克隆方案,让你的代码既安全又高效!不要再被简单的JSON方法限制住了,根据实际需求选择最合适的工具吧!