Object.assign
Object.assign
Object.assign
,将所有可枚举属性的值从一个或多个源对象复制到目标对象。并返回目标对象。
1 2 3 |
// 语法 Object.assign(target, ...sources) |
1 2 3 4 5 6 7 8 9 10 |
var target = {a: 1, b: 2}; var source1 = {b: 3, c: 4}; var source2 = {d: 5}; var ret = Object.assign(target, source1, source2); ret === target; // true console.log(ret); // {a: 1, b: 3, c: 4, d: 5} Object.assign(target, null, undefined); // 不会报错。只不过,没有属性添加进来 |
Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。
该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。
因此,它分配属性,而不仅仅是复制或定义新的属性。
如果合并源source包含getter,这可能使其不适合将新属性合并到原型中。
为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。
String类型和 Symbol 类型的属性都会被拷贝。
在出现错误的情况下,例如,如果属性不可写,会引发TypeError,如果在引发错误之前添加了任何属性,则可以更改target对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。 let obj1 = { a: 0 , b: { c: 0}}; let obj2 = Object.assign({}, obj1); obj2.b.c = 3; console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}} console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}} // 深拷贝 let obj3 = JSON.parse(JSON.stringify(obj1)); 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 } // o2,o3没有变化。 o1已经被修改。 所以,通常会使用 Object.assign({}, o1, o2, o3)的写法。 // o1是const常量,为什么能修改? 因为,const保存的是一个引用地址。 // 修改的时候,并不是修改这个地址。而是修改地址指向的堆内存中的内容。 // 拷贝 symbol类型的属性 const o1 = { a: 1 }; const o2 = { [Symbol('foo')]: 2 }; const obj = Object.assign({}, o1, o2); console.log(obj); // {a: 1, Symbol(foo): 2} Object.getOwnPropertySymbols(obj); // [Symbol(foo)] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 继承属性和不可枚举属性是不能拷贝的 const obj = Object.create({foo: 1}, { // foo 是个继承属性。 bar: { value: 2 // bar 是个不可枚举属性。 }, baz: { value: 3, enumerable: true // baz 是个自身可枚举属性。 } }); const copy = Object.assign({}, obj); console.log(copy); // { baz: 3 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 原始类型会被包装为对象 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" } // 注意,只有字符串的包装对象才可能有自身可枚举属性。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 异常会打断后续的拷贝操作 const target = Object.defineProperty({}, "foo", { value: 1, writable: false }); // target 的 foo 属性是个只读属性。 try{ Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); } catch(e){ console.log(e); // 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,第三个源对象更是不会被拷贝到的。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 拷贝访问器 const obj = { foo: 1, get bar() { return 2; } }; let copy = Object.assign({}, obj); console.log(copy); // { foo: 1, bar: 2 } 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); // { foo:1, get bar() { return 2 } } |
Polyfill
Polyfill请参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill