js 深拷贝和浅拷贝

101 阅读3分钟

浅拷贝常见方法

  • 特点:只复制对象第一层属性,浅套对象仍保留共享引用
  1. Object.assign()

    • 将原对象的可枚举属性复制到目标对象
    const obj = {a: 1, next: { b: 2 }}
    const copy = Object.assgin({}, obj)
    
  2. 展开运算符

    • 通过 ... 展开对象或数组
    const obj = { a: 1, nested: { b: 2 } };
    const copy = { ...obj };
    
    const arr = [1, [2, 3]];
    const arrCopy = [...arr];
    
  3. 数组的slice / concat / Array.from()

    • 适用于数组的浅拷贝
    const arr = [1, [2, 3]];
    const copy = arr.slice();
    const copy2 = [].concat(arr);
    const copy3 = Array.from(arr)
    

深拷贝

  • 特点:复制所有层级的属性,新旧对象完全独立不再有引用共享
  1. JSON.parse(JSON.stringify())
    • 通过JSON序列化再反序列化实现
    • 缺点
      1. 会跳过undefined / function / Symbol类型的值
      2. 无法处理循环引用
      3. 日期对象转为字符串,正则表达式丢失
    const sampleObject = {
        string: 'string',
        number: 123,
        boolean: false,
        null: null,
        undefined: undefined,  // 会被忽略 跳过
        fn: function() {}, // 会被忽略 跳过
        sy: Symbol(), // 会被忽略 跳过
        date: new Date('1999-12-31T23:59:59'),  // Date 将被转成字符串
        notANumber: NaN, // NaN 转成null
        infinity: Infinity,  // Infinity 转成null
        regExp: /.*/, // RegExp 转成空对象
    }
    const faultyClone = JSON.parse(JSON.stringify(sampleObject))
    
  2. structuredClone() (相对简单场景,优先使用)
    • 浏览器原生支持,支持更多类型(如Date,Set,Map等)
    • 缺点
      1. 不支持 函数 / Symbol / DOM 节点
      2. 需要较新的浏览器环境(或Node.js 17+)
    var sampleObject = {
        string: 'string',
        number: 123,
        boolean: false,
        null: null,
        undefined: undefined,
        date: new Date('1999-12-31T23:59:59'),
        notANumber: NaN,
        infinity: Infinity,
        regExp: /.*/,
        // fn: function() {}, // 报错
        // sy: Symbol(1), // 报错
    }
    var faultyClone = structuredClone(sampleObject)
    
    const obj2 = {c: function() {}};
    const copy2 = structuredClone(obj); 
    // 报错 Uncaught DataCloneError: Failed to execute 'structuredClone' on 'Window': function() {} could not be cloned.
    
    const obj3 = {sy: Symbol()};
    // 报错 Uncaught DataCloneError: Failed to execute 'structuredClone' on 'Window': Symbol(1) could not be cloned.
    
  3. 第三方库(如Lodash的._cloneDeep)(复杂场景,函数,特殊类型)
    • 使用成熟库处理复杂场景(如循环引用/特殊对象)
    const _ = require('lodash');
    const copy = _.cloneDeep(obj);
    
  4. 手动递归
    • 简单版:处理对象和数组
    const deepCopy = (obj) => {
        // 如果不是对象或者为 null,直接返回
        if (typeof obj !== "object" || obj === null) {
            return obj;
        }
        // 创建一个新对象或数组
        const newObj = Array.isArray(obj) ? [] : {};
    
        Object.keys(obj).forEach((key) => {
            newObj[key] = deepCopy(obj[key]);
        });
    
        return newObj;
    };
    
    // 测试对象和数组的深拷贝:
    const nestedArray = [[1], [2], [3]];
    
    const nestedCopyWithStructuredClone = deepCopy(nestedArray); // 深拷贝
    
    console.log(nestedArray === nestedCopyWithStructuredClone); // false
    
    nestedArray[0][0] = 4;
    console.log(nestedArray); // [[4], [2], [3]] 修改了原对象
    console.log(nestedCopyWithStructuredClone); // [[1], [2], [3]] 复制对象不受影响
    
    • 进阶版:处理循环引用

      • 什么事循环引用?
      const nestedObject = {
          name: 'Lily'
      }
      nestedObject.nestedObject = nestedObject; // 自己引用自己, 无限循环下去
      
      • 解决循环引用导致的栈内存溢出,需要用到WeakMap 记录已经复制过的对象
      const deepCopy = (obj, cache = new WeakMap()) => {
          // 如果不是对象或者为 null,直接返回
          if (typeof obj !== "object" || obj === null) {
              return obj;
          }
      
          // 如果已经复制过该对象,则直接返回
          if (cache.has(obj)) {
              return cache.get(obj);
          }
      
          // 创建一个新对象或数组
          const newObj = Array.isArray(obj) ? [] : {};
      
          // 将新对象放入 cache 中
          cache.set(obj, newObj);
      
          // 处理循环引用的情况
          Object.keys(obj).forEach((key) => {
              newObj[key] = deepCopy(obj[key], cache);
          });
      
          return newObj;
      };
      
      // 测试有循环引用的深拷贝:
      const nestedObject = {
          name: 'Lily'
      }
      nestedObject.nestedObject = nestedObject;
      
      const nestedCopyWithStructuredClone = deepCopy(nestedObject); // 深拷贝
      
      console.log(nestedObject === nestedCopyWithStructuredClone); // false
      
    • 最终版:处理特殊对象:Date 和 RegExp

    const deepCopy = (obj, cache = new WeakMap()) => {
        // 如果不是对象或者为 null,直接返回
        if (typeof obj !== "object" || obj === null) {
            return obj;
        }
    
        // 如果已经复制过该对象,则直接返回
        if (cache.has(obj)) {
            return cache.get(obj);
        }
    
        // 创建一个新对象或数组
        const newObj = Array.isArray(obj) ? [] : {};
    
        // 将新对象放入 cache 中
        cache.set(obj, newObj);
    
        // 处理特殊对象的情况
        if (obj instanceof Date) {
            return new Date(obj.getTime());
        }
    
        if (obj instanceof RegExp) {
            return new RegExp(obj);
        }
    
        // 处理循环引用的情况
        Object.keys(obj).forEach((key) => {
            newObj[key] = deepCopy(obj[key], cache);
        });
    
        return newObj;
    };
    
    // 测试 Date 和 regExp 的深拷贝:
    const date = new Date();
    const regExp = /test/g;
    
    const cloneDate = deepCopy(date); // 深拷贝
    const cloneRegExp = deepCopy(regExp); // 深拷贝
    
    console.log(cloneDate === date); // false
    console.log(cloneRegExp === regExp); // false