肥仔教程网

SEO 优化与 Web 开发技术学习分享平台

JavaScript深拷贝极简指南:3种方法解决嵌套与循环引用难题

为什么需要深拷贝?

首先我们看看浅拷贝,point指向的是同一个地址,这时我们修改obj2.point的属性时,obj1的point属性也会被修改

再看看深拷贝,point指向的是不同地址,这时我们修改obj2.point的属性时,obj1的point属性不受影响

总结一下深拷贝的特点:

  • 避免数据污染:修改副本时不影响原始数据
  • 解决嵌套问题:确保对象中的子对象被独立复制
  • 处理循环引用:当对象相互引用时不导致程序崩溃

三种深拷贝实用解决方案

1. JSON序列化法(适合简单数据)

const copy = JSON.parse(JSON.stringify(original));

优点:实现简单,一行代码搞定
局限:会丢失函数、Symbol等特殊对象类型

2. Lodash库方案(生产环境推荐)

import { cloneDeep } from 'lodash';
const copy = cloneDeep(original);

推荐理由:完整支持所有数据类型,经过大量项目验证

3. 手动实现方案(完全掌控型)

/**
 * 深度克隆对象
 * @param source 需要克隆的对象
 * @param weakMap 弱引用映射表用于处理循环引用(可选)
 * @returns 克隆后的新对象
 */
function deepClone<T>(source: T, weakMap = new WeakMap<any, any>()): T {
  // 处理基本数据类型和非对象类型
  if (
    source === null || 
    typeof source !== 'object' || 
    source instanceof Function
  ) {
    return source;
  }
  
    // 特殊对象处理
  if (source instanceof Date) return new Date(source);
  if (source instanceof RegExp) return new RegExp(source);

  // 处理循环引用
  if (weakMap.has(source)) {
    return weakMap.get(source);
  }
  
  const proto = Object.getPrototypeOf(source);
  const clone = Object.create(proto);
  weakMap.set(source, clone);

  // 克隆所有自有属性(包括Symbol和不可枚举属性)
  const allKeys = Reflect.ownKeys(source as object);
  
  for (const key of allKeys) {
    const descriptor = Object.getOwnPropertyDescriptor(source as object, key)!;
    
    if ('value' in descriptor) {
      // 克隆值属性
      Object.defineProperty(
        clone, 
        key, 
        Object.assign(descriptor, {
          value: deepClone((source as any)[key], weakMap)
        })
      );
    } else {
      // 复制访问器属性(getter/setter)
      Object.defineProperty(clone, key, descriptor);
    }
  }

  // 保持原型链完整性
  if ((source as any).__proto__ !== Object.prototype) {
    clone.__proto__ = (source as any).__proto__;
  }

  return clone;
}

关键技术要点

  • 循环引用:通过WeakMap缓存已拷贝对象
  • 函数处理:直接引用而非复制(保持函数特性)
  • 特殊对象:Date/RegExp等需调用构造函数
  • 原型链:使用Object.create保持原型关系
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言