Object.assign 的深浅拷贝问题

在ES6 (ECMAScript 2015)中,Object.assign() 是一个非常有用的方法,用于将一个或多个源对象的可枚举属性复制到目标对象。然而,关于它的拷贝行为是深拷贝还是浅拷贝,常常引起开发者的困惑。本文将深入探讨 Object.assign() 的拷贝机制,帮助开发者正确理解和使用这一方法。

Object.assign 的基本用法

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。其语法如下:

javascript 复制代码
Object.assign(target, ...sources)

示例:

javascript 复制代码
const target = { a: 1 };
const source = { b: 2 };

const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 2 }

浅拷贝的本质

Object.assign() 执行的是浅拷贝(shallow copy),这意味着:

  1. 对于基本数据类型(如字符串、数字、布尔值等),Object.assign() 会复制其值
  2. 对于引用数据类型(如对象、数组等),Object.assign() 只会复制其引用(内存地址),而不会递归复制嵌套对象
javascript 复制代码
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);

obj2.b.c = 3;
console.log(obj1.b.c); // 3 - 原始对象也被修改了

为什么不是深拷贝

深拷贝(deep copy)会递归复制对象的所有层级,创建一个完全独立的新对象。而 Object.assign() 的设计初衷并不是为了实现深拷贝,而是为了提供一种简单的方式来合并对象属性。

如果需要进行深拷贝,可以考虑以下方法:

  1. 使用 JSON.parse(JSON.stringify(object))(有局限性,不能处理函数、循环引用等)
  2. 使用专门的深拷贝工具函数(如 lodash 的 _.cloneDeep
  3. 实现自定义的递归拷贝函数

实际应用中的注意事项

  1. 修改目标对象Object.assign() 会直接修改目标对象

    javascript 复制代码
    const target = { a: 1 };
    Object.assign(target, { b: 2 });
    console.log(target); // { a: 1, b: 2 } - target被修改了
  2. 同名属性覆盖:后面的源对象属性会覆盖前面的

    javascript 复制代码
    const target = { a: 1, b: 1 };
    const source1 = { b: 2, c: 2 };
    const source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    console.log(target); // { a: 1, b: 2, c: 3 }
  3. 不可枚举属性Object.assign() 不会复制不可枚举属性

    javascript 复制代码
    const obj = Object.create({}, {
      enumerableProp: {
        value: 1,
        enumerable: true
      },
      nonEnumerableProp: {
        value: 2,
        enumerable: false
      }
    });
    
    const copy = Object.assign({}, obj);
    console.log(copy); // { enumerableProp: 1 }

与扩展运算符的比较

ES6 的对象扩展运算符 ... 在对象拷贝方面的行为与 Object.assign() 类似,也是执行浅拷贝:

javascript 复制代码
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 };

obj2.b.c = 3;
console.log(obj1.b.c); // 3 - 同样会影响原始对象

结论

Object.assign() 是一个强大的对象合并工具,但它执行的是浅拷贝而非深拷贝。理解这一点对于避免意外的副作用至关重要。在实际开发中,应根据需求选择合适的拷贝策略:

  • 需要简单合并对象属性时,使用 Object.assign() 或扩展运算符
  • 需要完全独立的副本时,使用深拷贝方法

正确理解 JavaScript 中的拷贝机制,可以帮助开发者写出更可靠、更易维护的代码。