JavaScript 深拷贝的完全解决方案

4 阅读8分钟

当我们说"深拷贝"时,我们到底在说什么?为什么简单的 JSON.parse(JSON.stringify(obj)) 不够用?如何优雅地处理循环引用、特殊对象、函数引用?本文将深入深拷贝的每一个角落,构建一个真正"完全"的深拷贝函数。

前言:一个看似简单的问题

我们先来看一个最简单的深拷贝:

const obj = { name: '张三', age: 25 };
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { name: '张三', age: 25 }

但这真的够用吗?如果我们有一个复杂对象:

const complexObj = {
  date: new Date(),
  regex: /test/gi,
  func: () => console.log('hello'),
  undef: undefined,
  inf: Infinity,
  nan: NaN,
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  symbol: Symbol('test'),
  error: new Error('错误')
};

这时候应该如何处理呢?这就是为什么我们需要一个真正完善的深拷贝解决方案。

深拷贝的基础概念

深拷贝 vs 浅拷贝

  • 深拷贝:完全独立的副本,修改拷贝对象的属性值,不会影响原对象
  • 浅拷贝:只复制一层,修改拷贝对象的属性值,会影响原对象

浅拷贝示例

const original = {
  name: '张三',
  address: {
    city: '北京',
    street: '长安街'
  }
};

// 浅拷贝示例
const shallowCopy1 = Object.assign({}, original);
const shallowCopy2 = { ...original };

shallowCopy1.address.city = '上海';
console.log('original.address.city:', original.address.city); // 上海
console.log('shallowCopy1.address.city:', shallowCopy1.address.city); // 上海

深拷贝示例

const original = {
  name: '张三',
  address: {
    city: '北京',
    street: '长安街'
  }
};
function simpleDeepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

const deepCopy = simpleDeepCopy(original);
deepCopy.address.city = '广州';
console.log('original.address.city:', original.address.city); // 北京
console.log('deepCopy.address.city:', deepCopy.address.city); // 广州

为什么要深拷贝?

  • 状态管理要求不可变数据
  • 撤销/重做功能需要保存历史快照
  • 复杂表单的草稿保存
  • 数据处理的隔离环境
  • 跨线程/跨进程通信

深拷贝的挑战

  • 循环引用:对象相互引用导致无限递归
  • 特殊对象:需要保留原型链和构造函数
  • 函数:通常不拷贝,但需要处理
  • Symbol:作为属性名和值的处理
  • 不可枚举属性:需要遍历所有属性描述符
  • 原型链:是否需要继承
  • 性能:大量数据的拷贝效率

JSON 方法的全面评估

JSON 序列化的局限性

  1. undefined 会被忽略
  2. symbol 会被忽略
  3. function 会被忽略
  4. 特殊数值会变成 null
  5. Date / RegExp / Error 等对象会转成字符串
  6. Map / Set / WeakMap / WeakSet 等会变成空对象
  7. TypedArray / ArrayBuffer 会变成对象
  8. 循环引用会导致错误

JSON 方法的适用场景

  • 纯数据对象(只有普通对象、数组、字符串、数字、布尔值)
  • 与后端 API 通信的数据交换
  • 简单的本地存储(localStorage)
  • 不需要保持原对象类型的临时副本
  • 数据结构已知且可控的内部模块

完整深拷贝要考虑的问题

1. 基础功能

  • 支持所有原始类型
  • 支持普通对象和数组
  • 处理循环引用
  • 处理原型链

2. 内置对象

  • Date:保持 Date 对象
  • RegExp:保持正则表达式
  • Map:保持键值对结构
  • Set:保持集合结构
  • Error:保留错误信息
  • Promise:处理状态
  • Symbol:作为值和属性名

3. 二进制数据

  • ArrayBuffer
  • TypedArray 所有类型
  • DataView
  • SharedArrayBuffer

4. 其他特性

  • 支持不可枚举属性
  • 支持属性 getter/setter
  • 支持冻结/密封对象
  • 支持自定义类实例
  • 性能优化

递归实现与循环引用检测

基础递归实现

function basicDeepCopy(source) {
  // 原始类型直接返回
  if (source === null || typeof source !== 'object') {
    return source;
  }

  // 数组或对象
  const target = Array.isArray(source) ? [] : {};

  // 递归复制每个属性
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = basicDeepCopy(source[key]);
    }
  }

  return target;
}

循环引用检测

function deepCopyWithCycleDetection(source, cache = new WeakMap()) {
  // 处理原始类型
  if (source === null || typeof source !== 'object') {
    return source;
  }

  // 检测循环引用
  if (cache.has(source)) {
    console.log('检测到循环引用,返回已缓存的对象');
    return cache.get(source);
  }

  // 创建目标对象
  const target = Array.isArray(source) ? [] : {};

  // 缓存当前对象
  cache.set(source, target);

  // 递归复制属性
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = deepCopyWithCycleDetection(source[key], cache);
    }
  }

  return target;
}

性能优化版本

function optimizedDeepCopy(source, cache = new Map()) {
  // 快速路径:原始类型
  if (source === null || typeof source !== 'object') {
    return source;
  }

  // 快速路径:Date
  if (source instanceof Date) {
    return new Date(source);
  }

  // 快速路径:RegExp
  if (source instanceof RegExp) {
    return new RegExp(source.source, source.flags);
  }

  // 循环引用检测
  if (cache.has(source)) {
    return cache.get(source);
  }

  // 根据类型创建目标对象
  let target;

  if (Array.isArray(source)) {
    target = [];
  } else if (source instanceof Map) {
    target = new Map();
  } else if (source instanceof Set) {
    target = new Set();
  } else if (source instanceof WeakMap || source instanceof WeakSet) {
    // WeakMap/WeakSet 无法遍历,返回新实例
    return new source.constructor();
  } else {
    // 普通对象:使用原对象的构造函数
    target = Object.create(Object.getPrototypeOf(source));
  }

  // 缓存当前对象
  cache.set(source, target);

  // 处理数组
  if (Array.isArray(source)) {
    for (let i = 0; i < source.length; i++) {
      target[i] = optimizedDeepCopy(source[i], cache);
    }
    return target;
  }

  // 处理 Map
  if (source instanceof Map) {
    for (let [key, value] of source) {
      target.set(
        optimizedDeepCopy(key, cache),
        optimizedDeepCopy(value, cache)
      );
    }
    return target;
  }

  // 处理 Set
  if (source instanceof Set) {
    for (let value of source) {
      target.add(optimizedDeepCopy(value, cache));
    }
    return target;
  }

  // 处理普通对象
  const keys = [...Object.keys(source), ...Object.getOwnPropertySymbols(source)];

  for (let key of keys) {
    const descriptor = Object.getOwnPropertyDescriptor(source, key);

    if (descriptor) {
      // 复制属性描述符
      Object.defineProperty(target, key, {
        ...descriptor,
        value: optimizedDeepCopy(descriptor.value, cache)
      });
    }
  }

  return target;
}

内置对象的深拷贝

class BuiltInCopier {
  // Date 对象
  static copyDate(date) {
    return new Date(date.getTime());
  }

  // RegExp 对象
  static copyRegExp(regexp) {
    const flags =
      (regexp.global ? 'g' : '') +
      (regexp.ignoreCase ? 'i' : '') +
      (regexp.multiline ? 'm' : '') +
      (regexp.dotAll ? 's' : '') +
      (regexp.unicode ? 'u' : '') +
      (regexp.sticky ? 'y' : '');

    return new RegExp(regexp.source, flags);
  }

  // Error 对象
  static copyError(error) {
    const copy = new error.constructor(error.message);
    copy.stack = error.stack;
    copy.name = error.name;
    return copy;
  }

  // Map 对象
  static copyMap(map, copyFn) {
    const result = new Map();
    map.forEach((value, key) => {
      result.set(copyFn(key), copyFn(value));
    });
    return result;
  }

  // Set 对象
  static copySet(set, copyFn) {
    const result = new Set();
    set.forEach(value => {
      result.add(copyFn(value));
    });
    return result;
  }

  // WeakMap 对象
  static copyWeakMap(weakMap) {
    // WeakMap 不可遍历,返回空实例
    return new WeakMap();
  }

  // WeakSet 对象
  static copyWeakSet(weakSet) {
    // WeakSet 不可遍历,返回空实例
    return new WeakSet();
  }

  // ArrayBuffer 对象
  static copyArrayBuffer(arrayBuffer) {
    const copy = arrayBuffer.slice(0);
    return copy;
  }

  // TypedArray 对象
  static copyTypedArray(typedArray) {
    return new typedArray.constructor(typedArray);
  }

  // DataView 对象
  static copyDataView(dataView) {
    return new DataView(
      this.copyArrayBuffer(dataView.buffer),
      dataView.byteOffset,
      dataView.byteLength
    );
  }

  // Promise 对象
  static copyPromise(promise) {
    // Promise 无法复制,返回新的 pending Promise
    return new Promise(() => { });
  }
}

处理自定义类和原型链

自定义类的深拷贝

function copyCustomClass(instance, cache = new WeakMap()) {
  if (cache.has(instance)) {
    return cache.get(instance);
  }

  // 获取构造函数
  const Constructor = instance.constructor;

  // 创建新实例
  let copy;

  try {
    // 尝试使用构造函数创建新实例
    copy = Object.create(Constructor.prototype);
    Constructor.apply(copy, []);
  } catch (error) {
    // 如果构造函数需要参数,则使用 Object.create
    copy = Object.create(Constructor.prototype);
  }

  cache.set(instance, copy);

  // 复制所有属性
  const allKeys = Reflect.ownKeys(instance);

  for (const key of allKeys) {
    const descriptor = Object.getOwnPropertyDescriptor(instance, key);

    if (descriptor) {
      if (descriptor.value !== undefined) {
        descriptor.value = comprehensiveDeepCopy(descriptor.value, cache);
      }
      Object.defineProperty(copy, key, descriptor);
    }
  }

  return copy;
}

原型链的完整处理

function deepCopyWithPrototype(source, cache = new WeakMap()) {
  if (source === null || typeof source !== 'object') {
    return source;
  }

  if (cache.has(source)) {
    return cache.get(source);
  }

  let target;

  // 获取完整的原型链
  const getPrototypeChain = (obj) => {
    const chain = [];
    let proto = Object.getPrototypeOf(obj);
    while (proto && proto !== Object.prototype) {
      chain.unshift(proto);
      proto = Object.getPrototypeOf(proto);
    }
    return chain;
  };

  // 重建原型链
  const buildPrototypeChain = (obj, chain) => {
    if (chain.length === 0) {
      return obj;
    }

    let current = obj;
    for (let i = 0; i < chain.length; i++) {
      const proto = chain[i];
      const protoCopy = Object.create(Object.getPrototypeOf(proto));

      // 复制原型上的属性
      const keys = Reflect.ownKeys(proto);
      for (const key of keys) {
        const descriptor = Object.getOwnPropertyDescriptor(proto, key);
        if (descriptor) {
          if (descriptor.value !== undefined) {
            descriptor.value = deepCopyWithPrototype(descriptor.value, cache);
          }
          Object.defineProperty(protoCopy, key, descriptor);
        }
      }

      Object.setPrototypeOf(current, protoCopy);
      current = protoCopy;
    }

    return obj;
  };

  // 处理不同类型
  if (source instanceof Date) {
    target = new Date(source);
  } else if (source instanceof RegExp) {
    target = new RegExp(source);
  } else if (Array.isArray(source)) {
    target = [];
  } else if (source instanceof Map) {
    target = new Map();
  } else if (source instanceof Set) {
    target = new Set();
  } else {
    // 普通对象:先创建空对象,再设置原型链
    target = {};
  }

  cache.set(source, target);

  // 获取并重建原型链
  const protoChain = getPrototypeChain(source);
  buildPrototypeChain(target, protoChain);

  // 复制自身属性
  const allKeys = Reflect.ownKeys(source);
  for (const key of allKeys) {
    const descriptor = Object.getOwnPropertyDescriptor(source, key);
    if (descriptor) {
      if (descriptor.value !== undefined) {
        descriptor.value = deepCopyWithPrototype(descriptor.value, cache);
      }
      Object.defineProperty(target, key, descriptor);
    }
  }

  return target;
}

最终完整解决方案


// 深拷贝配置选项
class DeepCopyOptions {
  constructor({
    copySymbols = true,
    copyNonEnumerables = true,
    preservePrototype = true,
    copyFunctions = false,
    copyWeakCollections = false,
    maxDepth = Infinity,
    onError = (error, key, value) => console.warn(`拷贝 ${key} 时出错:`, error)
  } = {}) {
    this.copySymbols = copySymbols;
    this.copyNonEnumerables = copyNonEnumerables;
    this.preservePrototype = preservePrototype;
    this.copyFunctions = copyFunctions;
    this.copyWeakCollections = copyWeakCollections;
    this.maxDepth = maxDepth;
    this.onError = onError;
  }
}

// 最终版深拷贝
function cloneDeep(source, options = new DeepCopyOptions(), depth = 0, cache = new WeakMap()) {
  // 深度限制
  if (depth >= options.maxDepth) {
    return source;
  }

  // 处理原始类型
  if (source === null || typeof source !== 'object') {
    // 处理函数(可选)
    if (typeof source === 'function' && options.copyFunctions) {
      // 简单的函数复制,不保证完全等价
      return new Function('return ' + source.toString())();
    }
    return source;
  }

  // 循环引用检测
  if (cache.has(source)) {
    return cache.get(source);
  }

  let target;

  try {
    // 根据类型创建目标对象
    const constructor = source.constructor;

    // 内置对象处理
    switch (constructor) {
      case Date:
        target = new Date(source);
        break;

      case RegExp:
        target = new RegExp(source.source, source.flags);
        target.lastIndex = source.lastIndex;
        break;

      case Error:
        target = new source.constructor(source.message);
        target.stack = source.stack;
        target.name = source.name;
        break;

      case Map:
        target = new Map();
        cache.set(source, target);
        source.forEach((value, key) => {
          target.set(
            cloneDeep(key, options, depth + 1, cache),
            cloneDeep(value, options, depth + 1, cache)
          );
        });
        return target;

      case Set:
        target = new Set();
        cache.set(source, target);
        source.forEach(value => {
          target.add(cloneDeep(value, options, depth + 1, cache));
        });
        return target;

      case WeakMap:
        target = options.copyWeakCollections ? new WeakMap() : source;
        cache.set(source, target);
        return target;

      case WeakSet:
        target = options.copyWeakCollections ? new WeakSet() : source;
        cache.set(source, target);
        return target;

      case ArrayBuffer:
        target = source.slice(0);
        break;

      case DataView:
        target = new DataView(
          cloneDeep(source.buffer, options, depth + 1, cache),
          source.byteOffset,
          source.byteLength
        );
        break;

      default:
        // 检查 TypedArray
        if (ArrayBuffer.isView(source) && !(source instanceof DataView)) {
          target = new constructor(
            cloneDeep(source.buffer, options, depth + 1, cache),
            source.byteOffset,
            source.length
          );
          break;
        }

        // 普通对象或数组
        if (constructor === Object || constructor === Array) {
          target = Array.isArray(source) ? [] : {};
        } else if (options.preservePrototype) {
          // 保持原型链
          target = Object.create(constructor.prototype);
        } else {
          target = {};
        }
        break;
    }
  } catch (error) {
    options.onError(error, 'constructor', source);
    target = Array.isArray(source) ? [] : {};
  }

  // 缓存当前对象
  cache.set(source, target);

  // 处理数组
  if (Array.isArray(source)) {
    for (let i = 0; i < source.length; i++) {
      try {
        target[i] = cloneDeep(source[i], options, depth + 1, cache);
      } catch (error) {
        options.onError(error, i, source[i]);
        target[i] = undefined;
      }
    }
    return target;
  }

  // 获取所有属性键
  const allKeys = [];

  if (options.copySymbols) {
    allKeys.push(...Object.getOwnPropertySymbols(source));
  }

  if (options.copyNonEnumerables) {
    allKeys.push(...Object.getOwnPropertyNames(source));
  } else {
    allKeys.push(...Object.keys(source));
  }

  // 复制属性
  for (const key of allKeys) {
    try {
      const descriptor = Object.getOwnPropertyDescriptor(source, key);

      if (descriptor) {
        if (descriptor.value !== undefined) {
          descriptor.value = cloneDeep(descriptor.value, options, depth + 1, cache);
        }
        Object.defineProperty(target, key, descriptor);
      }
    } catch (error) {
      options.onError(error, key, source[key]);
    }
  }

  return target;
}

// 便利函数
const deepClone = {
  // 快速克隆(适用于大多数场景)
  quick: (obj) => cloneDeep(obj),

  // 完整克隆(保留所有特性)
  full: (obj) => cloneDeep(obj, new DeepCopyOptions({
    copySymbols: true,
    copyNonEnumerables: true,
    preservePrototype: true,
    copyFunctions: false,
    copyWeakCollections: false
  })),

  // 严格克隆(尽可能完整)
  strict: (obj) => cloneDeep(obj, new DeepCopyOptions({
    copySymbols: true,
    copyNonEnumerables: true,
    preservePrototype: true,
    copyFunctions: true,
    copyWeakCollections: true
  })),

  // 数据克隆(只保留可序列化的数据)
  data: (obj) => cloneDeep(obj, new DeepCopyOptions({
    copySymbols: false,
    copyNonEnumerables: false,
    preservePrototype: false,
    copyFunctions: false,
    copyWeakCollections: false
  }))
};

结语

最好的深拷贝是不需要深拷贝。通过良好的架构设计、使用不可变数据、避免深层嵌套等方式,可以减少对深拷贝的需求。当必须使用时,选择合适的实现方案,既满足需求又不过度设计。对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!