学一下对象浅拷贝常用的Object.assign

对象浅拷贝有不少实现方法,下面就来学习一下Object.assign。

基本用法

 1 const one = {a: 1, b: 2}
 2 const two = {c: '3', d: '4'}
 3 var three = Object.assign({e: 5}, one, two)
 4 console.log(three)
 5 
 6 //Object
 7 e: 5
 8 a: 1b: 2
 9 c: "3"
10 d: "4"
11 __proto__: Object

Object.assign返回参数中各个对象属性合并后的一个对象

注意事项:

  1. 存在同名属性,后面的属性值会覆盖前面的属性值
    1 var o1 = { a: 1, b: 1, c: 1 };
    2 var o2 = { b: 2, c: 2 };
    3 var o3 = { c: 3 };
    4 
    5 var obj = Object.assign({}, o1, o2, o3);
    6 console.log(obj); // { a: 1, b: 2, c: 3 },屬性c為o3.c的值,最後一個出現的屬性c。
  2. symbol属性会被复制
    var o1 = { a: 1 };
    var o2 = { [Symbol('foo')]: 2 };
    
    var obj = Object.assign({}, o1, o2);
    console.log(obj);
    console.log(Object.getOwnPropertySymbols(obj)); 
    //{a: 1, Symbol(foo): 2}
    //[Symbol(foo)]
  3. 不可枚举的对象不会被合并
     1 var obj = Object.create({ foo: 1 }, { // foo 是 obj 的屬性鏈。也不会被Object.aaign复制
     2   bar: {
     3     value: 2  // bar 是不可列舉的屬性,因為enumerable預設為false。Objecr.creat默认
     4   },
     5   baz: {
     6     value: 3,
     7     enumerable: true  // baz 是自身可列舉的屬性。
     8   }
     9 });
    10 
    11 var copy = Object.assign({}, obj);
    12 console.log(copy); // { baz: 3 }
  4. 如果参数不是对象,则会先转成对象,然后返回。由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。如果只有一个参数,Object.assign会直接返回该参数。

    如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefinednull不在首参数,就不会报错。其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

     1 const obj = {a: 1};
     2 Object.assign(obj) === obj // true
     3 
     4 typeof Object.assign(2) // "object"
     5 
     6 Object.assign(undefined) // 报错
     7 Object.assign(null) // 报错
     8 
     9 let obj = {a: 1};
    10 Object.assign(obj, undefined) === obj // true
    11 Object.assign(obj, null) === obj // true
    b = Object.assign({a:1, b:3 }, {a:2,b:null} )
    console.log(b) //{a: 2, b: null}
    //注意这种情况可能会导致bug,所以后端尽量不返回null和undefined
    const v1 = 'abc';
    const v2 = true;
    const v3 = 10;
    
    const obj = Object.assign({}, v1, v2, v3);
    console.log(obj); // { "0": "a", "1": "b", "2": "c" }
    
    Object(true) // {[[PrimitiveValue]]: true}
    Object(10)  //  {[[PrimitiveValue]]: 10}
    Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

    上面代码中,v1v2v3分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性 [[PrimitiveValue]]上面,这个属性是不会被Object.assign拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。 

  5. 出现异常终止复制
     1 var target = Object.defineProperty({}, 'foo', {
     2   value: 1,
     3   writable: false
     4 }); // target.foo 是 read-only (唯讀)屬性
     5 
     6 Object.assign(target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
     7 // TypeError: "foo" 是 read-only
     8 // 在指派值給 target.foo 時,異常(Exception)會被拋出。
     9 
    10 console.log(target.bar);  // 2, 第一個來源物件複製成功。
    11 console.log(target.foo2); // 3, 第二個來源物件的第一個屬性複製成功。
    12 console.log(target.foo);  // 1, 異常在這裡拋出。
    13 console.log(target.foo3); // undefined, 複製程式已中斷,複製失敗。
    14 console.log(target.baz);  // undefined, 第三個來源物件也不會被複製。
  6.  对于单层属性,目标对象和源对象属性值的更改不会相互影响
     对于深层属性,即属性的key作为引用指向另一个对象。目标拷贝的是源对象的引用,目标对象和源对象属性值的更改会相互影
     1 const one = {a: 1, b: {c: 2, d: 3}}
     2 单层拷贝:
     3     var two = Object.assign({}, one)
     4     two.a = 1.1
     5     one.a = 1.2
     6 打印结果:
     7     one = {a: 1.2, b: {c: 2, d: 3}}
     8     two = {a: 1.1, b: {c: 2, d: 3}}
     9 
    10 深层拷贝:
    11     var two = Object.assign({}, one)
    12     two.b.c = 10
    13     one.b.d = 5
    14 打印结果:
    15     one = {a: 1.2, b: {c: 10, d: 5}}
    16     two = {a: 1.1, b: {c: 10, d: 5}}
  7. Object.assign可以用来处理数组,但是会把数组视为对象。
    1 Object.assign([1, 2, 3], [4, 5])
    2 // [4, 5, 3]

    上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1

  8. Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
    1 const source = {
    2   get foo() { return 1 }
    3 };
    4 const target = {};
    5 
    6 Object.assign(target, source)
    7 // { foo: 1 }

    上面代码中, source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。

使用场景

  1. 为对象添加属性
    class Point {
      constructor(x, y) {
        Object.assign(this, {x, y});
      }
    }
    
    等同于:
    class Point {
      constructor(x, y) {
        this.x = x
        this.y = y
      }
    }
  2. 为对象添加方法
     1 Object.assign(SomeClass.prototype, {
     2   someMethod(arg1, arg2) {
     3     ···
     4   },
     5   anotherMethod() {
     6     ···
     7   }
     8 });
     9 
    10 // 等同于下面的写法
    11 SomeClass.prototype.someMethod = function (arg1, arg2) {
    12   ···
    13 };
    14 SomeClass.prototype.anotherMethod = function () {
    15   ···
    16 };
  3. 克隆对象
     1 function clone(origin) {
     2   return Object.assign({}, origin);
     3 }
     4 上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
     5 
     6 不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
     7 
     8 
     9 function clone(origin) {
    10   let originProto = Object.getPrototypeOf(origin);
    11   return Object.assign(Object.create(originProto), origin);
    12 }

  4.为属性指定默认值

 1 const DEFAULTS = {
 2   logLevel: 0,
 3   outputFormat: 'html'
 4 };
 5 
 6 function processContent(options) {
 7   options = Object.assign({}, DEFAULTS, options);
 8   console.log(options);
 9   // ...
10 }

上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTSoptions合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。

注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。

 1 const DEFAULTS = {
 2   url: {
 3     host: 'example.com',
 4     port: 7070
 5   },
 6 };
 7 
 8 processContent({ url: {port: 8000} })
 9 // {
10 //   url: {port: 8000}
11 // }

上面代码的原意是将 url.port改成 8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。

课外探究

除了Object.assign,展开操作符也可以用作浅拷贝

1 let obj = {
2   one: 1,
3   two: 2,
4 }
5  
6 let newObj = { ...z };
7  
8 // { one: 1, two: 2 }

展开操作符和Object.assign相似,同名属性后面覆盖前面,只复制可枚举属性,多层对象内层拷贝的是引用(即浅拷贝)。但性能上,展开操作符却要强很多。

如图所示,在拷贝较多对象时,不建议使用Object.assign,展开运算符在性能上也许是更好的选择。

思考:编写函数实现展开运算符功能

 1 "use strict";
 2 
 3 var _extends = Object.assign || function (target) {
 4   for (var i = 1; i < arguments.length; i++) {
 5     var source = arguments[i];
 6     for (var key in source) {
 7       if (Object.prototype.hasOwnProperty.call(source, key)) {
 8         target[key] = source[key];
 9       }
10     }
11   }
12   return target;
13 };
14 
15 var obj = { a: 1, b: 2, c: { d: 3 } };
16 var shallowCopiedObj = _extends({}, obj);
 
原文地址:https://www.cnblogs.com/AwenJS/p/12398722.html