ES6中Object.assign() 方法

龍恩0707發表於2017-08-26

ES6中Object.assign() 方法

1. 物件合併
Object.assign 方法用於物件的合併,將源物件(source)的所有可列舉屬性,複製到目標物件上。
如下程式碼演示:

var target = {a: 0};
var source1 = {b: 1};
var source2 = {c: 2};

Object.assign(target, source1, source2);
console.log(target);  // 輸出 {a: 0, b: 1, c: 2}

1-1 如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。
如下程式碼:

var target = {a: 0, b: 1};
var source1 = {a:1, b: 2, c:3};
var source2 = {c:5};

Object.assign(target, source1, source2);

console.log(target); // 輸出 {a: 1, b: 2, c: 5}

1-2 如果只有一個target(目標物件),Object.assign會直接返回該物件,如下程式碼:

var o = {a: 0};
Object.assign(o);
console.log(o);  // {a: 0}

1-3 如果該引數不是物件,則會先轉成物件,然後返回。
先是這樣的,正常的返回是number型別。

var a = 1;
Object.assign(a);
console.log(a); // 1
console.log(typeof a); // number

然後直接判斷型別,返回的是object型別

console.log(typeof Object.assign(2)) // object

1-4 對於null, undefined 來說 無法轉換成Object,就會在控制檯下報錯,如下程式碼:

Object.assign(null);  // 報錯
Object.assign(undefined);  // 報錯

1-5 物件合併,如果源物件是null的話或者是undefined的話,那麼物件合併的時候不會報錯,直接會跳過該物件的合併,直接返回目標物件。
如下程式碼:

var obj = {a: 1};
console.log(Object.assign(obj, null) === obj); // true
console.log(obj); // {a: 1}

var obj = {a: 1};
console.log(Object.assign(obj, undefined) === obj); // true
console.log(obj); // {a: 1}

1-6 如果是數值,布林型,和字串合併物件的話,都不會報錯,但是字串會以陣列的形式表現。
先看數值合併物件如下程式碼:

var obj = {a: 1};
console.log(Object.assign(obj, 12) === obj); // true
console.log(obj); // {a: 1}

布林型合併物件如下程式碼:

var obj = {a: 1};
console.log(Object.assign(obj, true) === obj); // true
console.log(obj); // {a: 1}

字串合併物件如下程式碼:

var obj = {a: 1};
console.log(Object.assign(obj, "bcd") === obj); // true
console.log(obj); // {0: 'b', 1: 'c', 2: 'd', a: 1}

如上程式碼,只有字串和物件合併,這是因為只有字串有包裝物件,會產生可列舉型別屬性。比如如下程式碼:

console.log(Object('bcd')); // {0: "b", 1: "c", 2: "d", length: 3, [[PrimitiveValue]]: "bcd"}
console.log(Object(1111)); // {[[PrimitiveValue]]: 1111}
console.log(Object(true)); // {[[PrimitiveValue]]: true}

上面程式碼可以看到原始值都在包裝物件的內部屬性[[PrimitiveValue]]上,這個屬性沒有被Object.assign合併,只有字串的包裝物件會產生可列舉的屬性,屬性則會被合併。
但是Object.assign合併的屬性是有限的,只合並物件的自身的屬性(不合並繼承屬性),也不合並不可列舉的屬性。

2. Object.assign方法是淺拷貝

因此Object.assign方法是淺複製,不是深複製,也就是說,如果源物件某個屬性的值是物件,那麼目標物件拷貝的是這個物件的引用。
比如如下程式碼:

var o1 = {a: {b: 1} };
var o2 = Object.assign({}, o1);
o1.a.b = 2;
console.log(o2.a.b); // 2

如上程式碼,o1是一個物件,該物件的屬性也是一個物件,使用Object.assign拷貝o1物件到o2上來,然後手動改變o1物件的屬性值,那麼o2物件的屬性值也會被改變。

但是如果物件的屬性值不是一個物件的話,那麼就不會影響該值,如下程式碼:

var o1 = {a: 1};
var o2 = Object.assign({}, o1);
o1.a = 2;
console.log(o1); // {a: 2}
console.log(o2.a); // 1

但是如果源物件和目標物件有同名屬性的話,那麼Object.assign會直接替換該屬性。比如如下程式碼:

var target = {a: {b: 1}};
var source1 = {a: {b: 'hello'}};
Object.assign(target, source1);
console.log(target); // {a: {b: 'hello'}}

注意:Object.assign可以用來處理陣列,但是會把陣列視為物件。
也就是說物件裡面有鍵值對索引,如果把兩個陣列合並的話,那麼得到不是合併後新增的陣列,而是會把對應相同的鍵替換掉,如下使用陣列的demo程式碼如下:

var targetArrs = [1, 2, 3];
var sourceArrs = [4, 5];
Object.assign(targetArrs, sourceArrs);
console.log(targetArrs);  // [4, 5, 3]

如上程式碼,目標物件有1,2,3屬性,源物件有4,5值,如果使用Object.assign的話,那麼源物件的鍵4,5 和目標物件的1,2鍵是相同的,因此值直接替換掉。

3. Object.assign 常見使用在哪些地方?
    3-1 為物件新增屬性。比如如下程式碼:

class A {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

如上方法通過Object.assign方法,將x屬性和y屬性新增到A類的物件實列中。

   3-2 為物件新增方法

Object.assign(A.prototype, {
     xMethod(x, y) {
       ...
     },
     yMethod() {

     }
   });
 // 相當於如下程式碼:
 A.prototype.xMethod = function(x, y) {};
 A.prototype.yMethod = function() {}

   3-3 克隆物件

function clone(obj) {
   return Object.assign({}, obj);
}

3-4 合併多個物件
如下一開始的程式碼:

var target = {a: 0};
var source1 = {b: 1};
var source2 = {c: 2};
Object.assign(target, source1, source2);
console.log(target);  // 輸出 {a: 0, b: 1, c: 2}

4. 物件深度克隆
淺度克隆和深度克隆的含義如下:
淺度克隆: 原始型別為值傳遞,物件型別為引用傳遞。
深度克隆: 所有元素或屬性都是完全複製的,與原物件完全脫離,也就是說所有對於源物件的修改都不會反映到新物件中。反之,所有對於新物件的修改也不會反映到源物件中。
注意:Object.assign 是淺度克隆的。

4-1 ES5中我們可以通過遞迴的方式去呼叫函式來實現深度克隆。
程式碼如下:

function deepClone(obj) {
  var newObj = obj instanceof Array ? [] : {};
  for (var i in obj) {
    newObj[i] = Object.prototype.toString.call(obj[i]) === "[object Object]" ? deepClone(obj[i]) : obj[i];
  }
  return newObj;
}
var obj = {a: {b: 1} };
var newObj = deepClone(obj);
console.log(newObj);  // 列印輸出 {a: {b: 1}}

// 修改物件 obj
obj.a.b = 2;
console.log(obj);   // 列印輸出 {a: {b: 2}}
console.log(newObj);  // 列印輸出 {a: {b: 1}} 原物件的修改不會影響到新物件中

如上面的程式碼,在JS中,我們使用遞迴的方式,迴圈遍歷物件的所有屬性和方法,實現深度克隆,因此當我們深度克隆到新物件中的時候,再去更改源物件的屬性值的時候,不會影響到新物件。

物件和陣列一起的深度克隆

function typeOf(obj) {
  const toString = Object.prototype.toString;
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  };
  return map[toString.call(obj)];
}
function deepCopy(data) {
  const t = this.typeOf(data);
  let o,
    i;
  if (t === 'array') {
    o = [];
  } else if (t === 'object') {
    o = {};
  } else {
    return data;
  }
  if (t === 'array') {
    for (let i = 0; i < data.length; i++) {
      o.push(this.deepCopy(data[i]));
    }
  } else if (t === 'object') {
    for (i in data) {
      o[i] = this.deepCopy(data[i]);
    }
  }
  return o;
}

相關文章