zoukankan      html  css  js  c++  java
  • 学一下对象浅拷贝常用的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);
     
  • 相关阅读:
    晨读,难道只是为了完成任务而读的吗?
    集合还有这么优雅的运算法?
    Java中的TreeSet集合会自动将元素升序排序
    CSS3的几个变形案例……
    “老师,请您多关注一下我吧!!!”
    jsp中使用cookie时报错……
    为什么要有周考?周考是用来干什么的?
    今天,我们就来抽个奖!
    今天 ,给大家变个魔术!!!
    Google Maps Api 多坐标分类标记,在地图上显示海量坐标,并分组显示。
  • 原文地址:https://www.cnblogs.com/AwenJS/p/12398722.html
Copyright © 2011-2022 走看看