Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,且返回目标对象
Object.assign(target, ...sources)
target : 目标对象
sources:源对象
注意:如果目标对象属性具有相同的键,则目标对象属性被源对象属性覆盖。后面的源对象属性覆盖前面的源对象属性。
Object.assign()方法只会拷贝源对象自身的并且是可枚举的属性到目标对象。此方法使用源对象的【Get】和目标对象的【Set】,所以它会调用相关的getter和setter。即,此方法是分配属性,而不是复制或建立新属性。如果合并源包含getter,这可能使不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用:
Object.getOwnPropertyDescriptor()和Object.definePropert.
String类型和Symbol类型的属性都会被拷贝。在出现错误的情况下,例如,如果属性不可写,会引发TypeError,在引发错误之前添加了任何属性,则可以更改target对象。且Object.assign不会再那些sources对象值为null或undefined的时候报错。
实例:
// 复制一个对象
const obj1 = {a:1};
const copy = Object.assign({},obj1);
console.log(JSON.stringify(copy)); /* {"a":1} */
针对深拷贝问题,需要使用其他的方法。因为Object.assign拷贝的是属性值,当属性值是一个对象的引用时,那么它也只指向那个引用。
// 深拷贝
let obj1 = {a:1,b:{c:2}};
let obj2 = Object.assign({},obj1);
console.log(JSON.stringify(obj2)); /* {"a":1,"b":{"c":2}} */
obj1.a = 10;
console.log(JSON.stringify(obj1)); /* {"a":10,"b":{"c":2}} */
console.log(JSON.stringify(obj2)); /* {"a":1,"b":{"c":2}} */
obj2.a = 20;
console.log(JSON.stringify(obj1)); /* {"a":10,"b":{"c":2}} */
console.log(JSON.stringify(obj2)); /* {"a":20,"b":{"c":2}} */
obj2.b.c = 99;
console.log(JSON.stringify(obj1)); /* {"a":10,"b":{"c":99}} */
console.log(JSON.stringify(obj2)); /* {"a":20,"b":{"c":99}} */
// deep clone
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 5;
obj1.b.c = 55;
console.log(JSON.stringify(obj3)); /* {"a":10,"b":{"c":99}} */
合并对象
// 合并对象
const o1 = {a:1}
const o2 = {b:2}
const o3 = {c:3}
const obj = Object.assign(o1,o2,o3)
console.log(JSON.stringify(obj)); /* {"a":1,"b":2,"c":3} */
console.log(JSON.stringify(o1)); /* {"a":1,"b":2,"c":3} ,注意目标对象自身也会改变*/
拷贝symbol类型的属性
// 拷贝symbol类型的属性
const o1 = {a:1}
const o2 = {[Symbol('foo')]:2};
const obj = Object.assign({},o1,o2)
console.log(obj); /* {a: 1, Symbol(foo): 2} */
console.log(Object.getOwnPropertySymbols(obj)); /* [Symbol(foo)] */
继承属性和不可枚举属性是不能拷贝的
// 继承属性和不可枚举属性是不能拷贝的
const obj = Object.create({foo:1},{ // foo是继承属性
bar:{
value:2 // bar 不可枚举属性
},
baz:{
value:3,
enumerable:true // baz是个自身可枚举属性
}
});
const copy = Object.assign({},obj)
console.log(JSON.stringify(copy)); /* {"baz":3} */
原始类型会被包装为对象
// 原始类型会被包装为对象
const o1 = 'abc';
const o2 = true;
const o3 = 10;
const o4 = Symbol("foo");
/*
* 原始类型会被包装,null、undefined被忽略。
* 只有字符串的包装对象才可能有自身可枚举属性。
* */
const obj = Object.assign({},o1,null,o2,undefined,o3,o4)
console.log(JSON.stringify(obj)); /* {"0":"a","1":"b","2":"c"} */
异常会打断后续拷贝任务
// 异常会打断后续拷贝任务
const target = Object.defineProperty({},"foo",{
value:1,
writable:false
}); // target的foo属性是个只读属性。
Object.assign(target,{bar:2},{foo2:3,foo:3,foo3:3},{baz:4})
/*
* TypeError:"foo" is read-only
* 注意这个异常只在拷贝第二个源对象的第二个属性时发生
* */
console.log(target.bar); // 2,说明第一个源对象拷贝成功了。
console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
console.log(target.foo); // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。
拷贝访问器
// 拷贝访问器
const obj = {
foo:1,
get bar(){
return 2;
}
};
let copy = Object.assign({},obj)
console.log(copy); // copy.bar的值来自obj.bar的getter函数的返回值
// 下面这个函数会拷贝所有自有属性的属性描述符
function completeAssign(target, ...sources) {
sources.forEach(source => {
let descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
// Object.assign 默认也会拷贝可枚举的Symbols
Object.getOwnPropertySymbols(source).forEach(sym => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
copy = completeAssign({}, obj);
console.log(copy);