JavaScript深浅拷贝原理与应用场景

在JavaScript中,深拷贝和浅拷贝是处理对象和数组复制的两种不同方式,理解它们的原理对于避免潜在的bug非常重要。

浅拷贝(Shallow Copy)

浅拷贝只复制对象或数组的第一层属性,如果属性是基本类型(如numberstringboolean等),则直接复制值;如果属性是引用类型(如objectarray等),则复制引用(即内存地址),而不是实际的对象或数组。

浅拷贝的实现方式
  1. 使用Object.assign()

    let obj = { a: 1, b: { c: 2 } };
    let shallowCopy = Object.assign({}, obj);

  2. 使用展开运算符(...

    let obj = { a: 1, b: { c: 2 } };
    let shallowCopy = { ...obj };

  3. 数组的浅拷贝

    let arr = [1, 2, { a: 3 }];
    let shallowCopy = arr.slice();
    // 或者
    let shallowCopy = [...arr];

浅拷贝的问题

浅拷贝后,如果修改了拷贝对象中的引用类型属性,原始对象中的对应属性也会被修改,因为它们共享同一个引用。

let obj = { a: 1, b: { c: 2 } };
let shallowCopy = { ...obj };

shallowCopy.b.c = 3;
console.log(obj.b.c); // 输出 3,原始对象也被修改了

深拷贝(Deep Copy)

深拷贝会递归地复制对象或数组的所有层级,生成一个完全独立的新对象或数组,新对象和原始对象之间没有任何共享的引用。

深拷贝的实现方式
  1. 使用JSON.parse(JSON.stringify())
    这种方法简单,但有一些局限性,比如不能复制函数、undefinedSymbol等。

    let obj = { a: 1, b: { c: 2 } };
    let deepCopy = JSON.parse(JSON.stringify(obj));

  2. 使用递归函数
    手动实现一个递归函数来深拷贝对象或数组。

    function deepCopy(obj) {
        if (typeof obj !== 'object' || obj === null) {
            return obj;
        }
        let copy = Array.isArray(obj) ? [] : {};
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                copy[key] = deepCopy(obj[key]);
            }
        }
        return copy;
    }
    
    let obj = { a: 1, b: { c: 2 } };
    let deepCopy = deepCopy(obj);

  3. 使用第三方库
    一些第三方库如lodash提供了深拷贝的功能。

    let _ = require('lodash');
    let obj = { a: 1, b: { c: 2 } };
    let deepCopy = _.cloneDeep(obj);

深拷贝的优点

深拷贝后,新对象和原始对象完全独立,修改新对象不会影响原始对象。

let obj = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(obj));

deepCopy.b.c = 3;
console.log(obj.b.c); // 输出 2,原始对象未被修改

深拷贝在大多数情况下可以避免数据丢失的问题,但在处理函数、undefinedSymbol、循环引用、特殊对象和原型链时,可能会导致数据丢失或不完整。以下是一些深拷贝可能遇到的问题:

1. 函数(Function)

  • 问题JSON.parse(JSON.stringify()) 方法无法复制函数。

  • 示例

    let obj = {
      a: 1,
      b: function() { console.log('Hello'); }
    };
    let deepCopy = JSON.parse(JSON.stringify(obj));
    console.log(deepCopy.b); // 输出 undefined

2. undefined

  • 问题JSON.parse(JSON.stringify()) 会忽略 undefined

  • 示例

    let obj = {
      a: 1,
      b: undefined
    };
    let deepCopy = JSON.parse(JSON.stringify(obj));
    console.log(deepCopy.b); // 输出 undefined

3. Symbol

  • 问题JSON.parse(JSON.stringify()) 无法处理 Symbol 类型的属性。

  • 示例

    let obj = {
      a: 1,
      [Symbol('key')]: 'value'
    };
    let deepCopy = JSON.parse(JSON.stringify(obj));
    console.log(deepCopy); // 输出 { a: 1 }

4. 循环引用

  • 问题:如果对象中存在循环引用(即对象属性引用了自身或其父对象),JSON.parse(JSON.stringify()) 会抛出错误。

  • 示例

    let obj = { a: 1 };
    obj.self = obj;
    let deepCopy = JSON.parse(JSON.stringify(obj)); // 抛出错误

5. 特殊对象

  • 问题:一些特殊对象如 DateRegExpMapSet 等,在 JSON.parse(JSON.stringify()) 中会被转换为字符串或其他形式,导致信息丢失。

  • 示例

    let obj = {
      date: new Date(),
      regex: /abc/g,
      map: new Map([['key', 'value']]),
      set: new Set([1, 2, 3])
    };
    let deepCopy = JSON.parse(JSON.stringify(obj));
    console.log(deepCopy.date); // 输出字符串形式的日期
    console.log(deepCopy.regex); // 输出 {}
    console.log(deepCopy.map); // 输出 {}
    console.log(deepCopy.set); // 输出 {}

6. 原型链

  • 问题JSON.parse(JSON.stringify()) 会丢失对象的原型链信息。

  • 示例

    function MyClass() {
      this.a = 1;
    }
    MyClass.prototype.method = function() { console.log('Hello'); };
    let obj = new MyClass();
    let deepCopy = JSON.parse(JSON.stringify(obj));
    console.log(deepCopy instanceof MyClass); // 输出 false

解决方案

为了处理这些特殊情况,可以使用更复杂的深拷贝方法,例如手动实现递归深拷贝函数,或者使用第三方库如 lodash 的 cloneDeep 方法。

手动实现递归深拷贝
function deepCopy(obj, hash = new WeakMap()) {
  // 如果是基本类型或 null,直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 如果已经拷贝过该对象,直接返回拷贝结果
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 创建新的对象或数组
  let copy = Array.isArray(obj) ? [] : {};

  // 将当前对象和拷贝结果存入 hash
  hash.set(obj, copy);

  // 递归拷贝所有属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepCopy(obj[key], hash);
    }
  }

  // 返回拷贝结果
  return copy;
}

let obj = {
  a: 1,
  b: { c: 2 },
  d: new Date(),
  e: /abc/g,
  f: function() { console.log('Hello'); },
  g: Symbol('key'),
  h: undefined
};
obj.self = obj;

let deepCopy = deepCopy(obj);
console.log(deepCopy);
使用 lodash 的 cloneDeep
let _ = require('lodash');
let obj = {
  a: 1,
  b: { c: 2 },
  d: new Date(),
  e: /abc/g,
  f: function() { console.log('Hello'); },
  g: Symbol('key'),
  h: undefined
};
obj.self = obj;

let deepCopy = _.cloneDeep(obj);
console.log(deepCopy);

总结

  • 浅拷贝:只复制第一层属性,引用类型的属性共享同一个引用。

  • 深拷贝:递归复制所有层级的属性,生成一个完全独立的对象。

根据具体需求选择合适的拷贝方式,避免因引用共享导致的意外修改。

你可能感兴趣的:(javascript,前端)