javascript系列--Object.assign完成浅拷贝的道理以及完成

一、媒介

之前在前面一篇进修了赋值,浅拷贝和深拷贝。引见了这三者的相干学问和区分。

传送门:https://www.mwcxs.top/page/59…

本文会引见浅拷贝Object.assign()的完成道理,然后我们试着完成一个浅拷贝。

二、浅拷贝Object.assign()

什么是浅拷贝?浅拷贝就是建立一个新对象,这个对象有着原始对象属性值的一份准确拷贝。

浅拷贝Object.assign()是什么?主要将一切可罗列属性的值从一个或许多个数据源对象复制到目的对象,同时返回目的对象。

语法划定规矩:

Object.assign(target,...sources)

个中target是目的对象,source是源对象,可所以多个,修正返回的是目的对象target。

1、假如目的对象中的属性具有雷同的属性键,则属性将被源对象中的属性掩盖;

2、源对象的属相将相似掩盖新近的属性。

强调两点:

1、可罗列的属性(自有属性)

2、string或许symbol范例是能够被直接分派的

2.1栗子1

浅拷贝就是拷贝第一层的基础范例值,以及第一层的援用范例地点。

// saucxs
// 第一步
let a = {
    name: "advanced",
    age: 18
}
let b = {
    name: "saucxs",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "saucxs",
//  age: 18,
//     book: {title: "You Don't Know JS", price: "45"}
// }
console.log(a === c);
// true

// 第二步
b.name = "change";
b.book.price = "55";
console.log(b);
// {
//     name: "change",
//     book: {title: "You Don't Know JS", price: "55"}
// }

// 第三步
console.log(a);
// {
//     name: "saucxs",
//  age: 18,
//     book: {title: "You Don't Know JS", price: "55"}
// }

剖析:

1、第一步中,运用Object.assign把源对象b的值复制到目的对象a中,这里把返回值定义为对象c,能够看出b会替换掉a中具有雷同键的值,即假如目的对象a中的属性具有雷同的键,则属相将被源对象b中的属性掩盖。返回的对象c就是目的对象a。

2、第二步中,修正源对象b的基础范例值(name)和援用范例值(book)。

3、第三步中,浅拷贝以后目的对象a的基础范例值没有转变,然则援用范例值发生了转变,由于Object.assign()拷贝的是属性值。到场源对象的属性值是一个指向对象的援用,只拷贝谁人援用地点。

2.2栗子2

string范例和symbol范例的属性都会被拷贝,而且不会跳过那些值为null或undefined的源对象。

// saucxs
// 第一步
let a = {
    name: "saucxs",
    age: 18
}
let b = {
    b1: Symbol("saucxs"),
    b2: null,
    b3: undefined
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "saucxs",
//  age: 18,
//     b1: Symbol(saucxs),
//     b2: null,
//     b3: undefined
// }
console.log(a === c);
// true

三、Object.assign模仿完成

完成Object.assign模仿完成大抵思绪:

1、推断原生的Object是不是支撑assign这个函数,假如不存在的话就会建立一个assign函数,并运用Object.defineProperty将函数绑定到Object上。

2、推断参数是不是准确(目的参数不能为空,能够直接设置{}通报进去,然则必需有值)。

3、运用Object()转成对象,并保存为to,末了返回这个对象to。

4、运用for in 轮回遍历出一切的可罗列的自有属性,并复制给新的目的对象(运用hasOwnProperty猎取自有属性,即非原型链上的属性)

参考原生,完成代码以下,运用assign2替代assign。此处的模仿不支撑symbol属性,由于es5中没有symbol。

// saucxs
if (typeof Object.assign2 != 'function') {
  // 注重 1
  Object.defineProperty(Object, "assign2", {
    value: function (target) {
      'use strict';
      if (target == null) { // 注重 2
        throw new TypeError('Cannot convert undefined or null to object');
      }

      // 注重 3
      var to = Object(target);
        
      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) {  // 注重 2
          // 注重 4
          for (var nextKey in nextSource) {
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

测试一下:

// saucxs
// 测试用例
let a = {
    name: "advanced",
    age: 18
}
let b = {
    name: "saucxs",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let c = Object.assign2(a, b);
console.log(c);
// {
//     name: "saucxs",
//  age: 18,
//     book: {title: "You Don't Know JS", price: "45"}
// } 
console.log(a === c);
// true

3.1 注重1:可罗列性

原生状况下挂载在Object上的属性时不可罗列的,然则直接在Object上挂载属性a以后就能够罗列的,所以必需运用Object.defineProperty,并设置enumerable: false 以及 writable: trueconfigurable: true

// saucxs
for(var i in Object) {
    console.log(Object[i]);
}
// 无输出

Object.keys( Object );
// []

上面申明,原生的Object上的属性不可罗列。

我们能够运用2种要领检察Object.assign是不是可罗列,运用Object.getOwnPropertyDescriptor或许Object.propertyIsEnumberable都能够,个中propertyIsEnumerable(..)会搜检给定的属性名是不是直接存在于对象中(而不是在原型链上)而且满足enumerable:true。详细用法以下:

// saucxs
Object.getOwnPropertyDescriptor(Object, "assign");
// {
//     value: ƒ, 
//  writable: true,     // 可写
//  enumerable: false,  // 不可罗列,注重这里是 false
//  configurable: true    // 可设置
// }
// saucxs
Object.propertyIsEnumerable("assign");
// false

申明Object.assign是不可罗列的。

直接在Object上挂载属性a以后是能够罗列的。我们来看一下代码:

// saucxs
Object.a = function () {
    console.log("log a");
}

Object.getOwnPropertyDescriptor(Object, "a");
// {
//     value: ƒ, 
//  writable: true, 
//  enumerable: true,  // 注重这里是 true
//  configurable: true
// }

Object.propertyIsEnumerable("a");
// true

所以要完成 Object.assign 必需运用 Object.defineProperty,并设置 writable: true, enumerable: false, configurable: true,固然默许状况下不设置就是 false

// saucxs
Object.defineProperty(Object, "b", {
    value: function() {
        console.log("log b");
    }
});

Object.getOwnPropertyDescriptor(Object, "b");
// {
//     value: ƒ, 
//  writable: false,     // 注重这里是 false
//  enumerable: false,  // 注重这里是 false
//  configurable: false    // 注重这里是 false
// }

模仿完成涉及到代码

// saucxs
// 推断原生 Object 中是不是存在函数 assign2
if (typeof Object.assign2 != 'function') {
  // 运用属性描述符定义新属性 assign2
  Object.defineProperty(Object, "assign2", {
    value: function (target) { 
      ...
    },
    // 默许值是 false,即 enumerable: false
    writable: true,
    configurable: true
  });
}

3.2 注重2:推断参数是不是准确

有些文章推断参数是不是准确是如许的。

// saucxs
if (target === undefined || target === null) {
    throw new TypeError('Cannot convert undefined or null to object');
}

如许一定没题目,然则如许写没有必要,由于 undefinednull 是相称的(高程 3 P52 ),即 undefined == null 返回 true,只需要根据以下体式格局推断就好了。

// saucxs
if (target == null) { // TypeError if undefined or null
    throw new TypeError('Cannot convert undefined or null to object');
}

3.3 注重3:原始范例被包装为对象

// saucxs
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo");

var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 

// 原始范例会被包装,null 和 undefined 会被疏忽。
// 注重,只要字符串的包装对象才能够有本身可罗列属性。
console.log(obj); 
// { "0": "a", "1": "b", "2": "c" }

上面代码中的源对象 v2、v3、v4 实际上被疏忽了,缘由在于他们本身没有可罗列属性

// saucxs
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo");
var v5 = null;

// Object.keys(..) 返回一个数组,包括一切可罗列属性
// 只会查找对象直接包括的属性,不查找[[Prototype]]链
Object.keys( v1 ); // [ '0', '1', '2' ]
Object.keys( v2 ); // []
Object.keys( v3 ); // []
Object.keys( v4 ); // []
Object.keys( v5 ); // TypeError: Cannot convert undefined or null to object

上面代码申明:Object.keys(..)返回一个数组,包括一切可罗列的属性,只会查找对象直接包括的属性,而不会查找[[prototype]]链。

// Object.getOwnPropertyNames(..) 返回一个数组,包括一切属性,不管它们是不是可罗列
// 只会查找对象直接包括的属性,不查找[[Prototype]]链
Object.getOwnPropertyNames( v1 ); // [ '0', '1', '2', 'length' ]
Object.getOwnPropertyNames( v2 ); // []
Object.getOwnPropertyNames( v3 ); // []
Object.getOwnPropertyNames( v4 ); // []
Object.getOwnPropertyNames( v5 ); 
// TypeError: Cannot convert undefined or null to object

上面代码申明:Object.getOwnPropertyNames(..)返回一个数组,庇护焊一切属性,不管他们是不是能够罗列,只会查找对象直接包括的属性,不查找[[prototype]]链。

然则如许是能够实行的:

// saucxs
var a = "abc";
var b = {
    v1: "def",
    v2: true,
    v3: 10,
    v4: Symbol("foo"),
    v5: null,
    v6: undefined
}

var obj = Object.assign(a, b); 
console.log(obj);
// { 
//   [String: 'abc']
//   v1: 'def',
//   v2: true,
//   v3: 10,
//   v4: Symbol(foo),
//   v5: null,
//   v6: undefined 
// }

为何?由于undefined,true等不适作为对象,而是作为对象b的属性值,对象b是可罗列的。

// saucxs
// 接上面的代码
Object.keys( b ); // [ 'v1', 'v2', 'v3', 'v4', 'v5', 'v6' ]

这里实在又能够看出一个题目来,那就是目的对象是原始范例,会包装成对象,对应上面的代码就是目的对象 a 会被包装成 [String: 'abc'],那模仿完成时应当怎样处置惩罚呢?很简单,运用 Object(..) 就能够了。

// saucxs
var a = "abc";
console.log( Object(a) );
// {0: 'a', 1: 'b', 2: 'c'}

我们再来看看下面代码能不能实行:

// saucxs
var a = "abc";
var b = "def";
Object.assign(a, b); // TypeError: Cannot assign to read only property '0' of object '[object String]'

照样会报错的,缘由在于:Object(‘abc’)时刻,其属性描述符writable为不可写,即writeable: false。

// saucxs
var myObject = Object( "abc" );

Object.getOwnPropertyNames( myObject );
// [ '0', '1', '2', 'length' ]

Object.getOwnPropertyDescriptor(myObject, "0");
// { 
//   value: 'a',
//   writable: false, // 注重这里
//   enumerable: true,
//   configurable: false 
// }

3.4 注重4:存在性

怎样在不接见属性值的状况下推断对象中是不是存在某个属性,看下面代码:

// saucxs
var anotherObject = {
    a: 1
};

// 建立一个关联到 anotherObject 的对象
var myObject = Object.create( anotherObject );
myObject.b = 2;

("a" in myObject); // true
("b" in myObject); // true

myObject.hasOwnProperty( "a" ); // false
myObject.hasOwnProperty( "b" ); // true

运用in和hasOwnProperty要领,区分以下:

1、in 操作符会搜检属性是不是在对象及其[[propertype]]原型链中;

2、hasOwnProperty(..)只会搜检是不是在myObject对象中,不会搜检[[prototype]]原型链中。

Object.assign要领一定是不会拷贝原型链上的属性,所以模仿完成时需要用hasOwnProperty(..)推断处置惩罚下,然则直接运用myObject.hasOwnProperty(..)是有题目的,由于有的对象能够没有连接到Object.prototype上(经由过程Object.create(null)来建立),这类状况下,运用myObject.hasOwnProperty(..)就会失利。

// saucxs
var myObject = Object.create( null );
myObject.b = 2;

("b" in myObject); 
// true

myObject.hasOwnProperty( "b" );
// TypeError: myObject.hasOwnProperty is not a function

解决办法,运用call就能够了,以下:

// saucxs
var myObject = Object.create( null );
myObject.b = 2;

Object.prototype.hasOwnProperty.call(myObject, "b");
// true
所以详细到本次模仿完成中,相干代码以下。

// saucxs
// 运用 for..in 遍历对象 nextSource 猎取属性值
// 此处会同时搜检其原型链上的属性
for (var nextKey in nextSource) {
    // 运用 hasOwnProperty 推断对象 nextSource 中是不是存在属性 nextKey
    // 过滤其原型链上的属性
    if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
        // 赋值给对象 to,并在遍历完毕后返回对象 to
        to[nextKey] = nextSource[nextKey];
    }
}

四、参考

1、MDN的Object.assign()

2、明白Object.assign()

    原文作者:saucxs
    原文地址: https://segmentfault.com/a/1190000019224263
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞