「Object.assign」详解 之 源码彻底吃透

564 阅读3分钟

Object.assign是什么?

我们先了解一下Object.assign是什么?

MDN的定义:Object.assign()  方法将所有可枚举和自有属性从一个或多个源对象复制到目标对象,返回目标对象。

Object.assign的使用

Object.assign(target, ...sources)

参数

  • target

    目标对象,接收源对象属性的对象,也是修改后的返回值。

  • sources

    源对象,包含将被合并的属性。

返回值

  • 目标对象 (返回的是目标对象,即第一个参数!)

关于深拷贝

如果需要深拷贝,则需要使用其他办法,因为 Object.assign() 对于源对象里的引用数据类型是无法进行深拷贝的。假如源对象里是一个对象的引用,它仅仅会复制其引用值,并不会重新开辟一个新的内存地址。

可以理解为,合并之后源对象里的引用数据类型是浅拷贝,基本数据类型是深拷贝。

  let obj1 = { a: 0 , b: { c: 0}};
  let obj2 = Object.assign({}, obj1);
  console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0}}

  obj1.a = 1;//改变对象中的基本数据类型 双方不影响
  console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 0}}
  console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0}}

  obj2.b.c = 3;//改变对象中的引用数据类型 双方都变了
  console.log(JSON.stringify(obj1)); // { "a": 1, "b": { "c": 3}}
  console.log(JSON.stringify(obj2)); // { "a": 2, "b": { "c": 3}}

所以说Object.assign() 并不适用于深拷贝;可以用JSON.parse(JSON.stringify())或者 递归的方式进行数据的深拷贝。

合并对象

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 } -- 返回目标对象o1
console.log(o1);  // { a: 1, b: 2, c: 3 } -- o1目标对象已经被改变了,不再是{ a: 1 };
console.log(obj == o1); // true 

如果我们想不改变源对象可以这样合并对象

const o1 = { a: 1 };
const o2 = { b: 2 };

const obj = Object.assign({},o1, o2);
console.log(obj); // { a: 1, b: 2 } -- 返回合并过后的目标对象
console.log(o1);  // { a: 1} -- 源对象o1没有发生变化
console.log(obj == o1); // false 

合并具有相同属性的对象

  • 合并相同属性的源对象,合并过程中后面的源对象属性会替换前面源对象的属性
const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };

const obj = Object.assign({}, o1, o2);
console.log(obj); // { a: 1, b: 2, c: 2 }

原型链上的属性和不可枚举属性不能被复制

const obj = Object.create({ foo: 1 }, { // foo 是 obj 的原型链
  bar: {
    value: 2  // bar 是不可枚举属性,因为没有设置enumerable,默认为不可枚举
  },
  baz: {
    value: 3,
    enumerable: true  // baz是可枚举的
  }
});
const copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }  bar没有合并过来


//什么是不可枚举?简单理解为是不能够一个个地列举
let keys = []
for (let key in obj) { //不可枚举的属性则不能遍历
  keys.push(key)
}
console.log(keys);//['bat', 'foo'] bar则没有遍历出来

基本类型会被包装为对象

const v1 = 'abc';
const v2 = true;
const v3 = 10;
const v4 = Symbol('foo');

const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// null 和 undefined 会被忽略
// 只有字符串包装器才能有自己的可枚举属性
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

手写源码

function objectAssign(target) {
  if (arguments.length === 1) return target
  let source = Array.prototype.slice.call(arguments, 1)
  source.map(object => {
    for (const key in object) {
      if (Object.hasOwnProperty.call(object, key)) {
        target[key] = object[key];
      }
    }
  })
  return target
}

Object.assign的兼容性

图片.png

Object.assign的注意事项

  1. 合并时,目标对象自身也会改变
  2. 拷贝是浅拷贝,不能作为深拷贝
  3. 继承属性和不可枚举属性不能拷贝
  4. 如果有同名属性的话,后面的属性值会覆盖前面的属性值
  5. 异常会打断后续拷贝任务

如有小伙伴发现错误之处欢迎指正🤚