對象的合併及拷貝

對象的合併及拷貝

Object.assign()

Object.assign() 方法用於對象的合併,將所有自身的(非繼承的)可枚舉屬性的值從一個或多個源對象拷貝到目標對象。返回目標對象。目標對象自身也會改變。

Object.assign(target, ...sources)
  • target: 目標對象。
  • sources: 源對象。

Object.assign() 合併拷貝屬性的限制

只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。

Object.assign({b: 'c'},
  Object.defineProperty({}, 'invisible', {
    enumerable: false,
    value: 'hello'
  })
)
// { b: 'c' }

參數類型

1. 只有一個參數

如果只有一個參數,Object.assign() 會直接返回該參數。

let obj = {a: 1};
Object.assign(obj) === obj // true

如果該參數不是對象,則會先轉成對象,然後返回。

typeof Object.assign(2) // "object"

由於 undefinednull 無法轉成對象,所以如果它們作爲參數,就會報錯。

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

2. 對象 + 非對象

非對象參數都會轉成對象,如果無法轉成對象,就會跳過,不會報錯。

如果非對象參數爲 undefinednull ,就會跳過,不會報錯,返回的依舊是目標對象參數。

let obj = {a: 1};
Object.assign(obj, undefined) === obj    // true
Object.assign(obj, null) === obj         // true

如果非對象參數爲其他類型的值(即數值、字符串和布爾值),也不會報錯。但是,除了字符串會以數組形式拷貝入目標對象,其他值都不會產生效果。這是因爲只有字符串的包裝對象,會產生可枚舉屬性。

let v1 = 'abc';
let v2 = true;
let v3 = 10;

let obj = Object.assign({}, v1, v2, v3);
console.log(obj)  // { "0": "a", "1": "b", "2": "c" }

3. 目標對象 + 源對象...

(1) 屬性值爲 nullundefined 的屬性會正常合併

Object.assign() 不會跳過那些屬性值爲 nullundefined 的源對象。

var o1 = { a: null, b: 1};
var o2 = { c: undefined };
    
var obj = Object.assign({}, o1, o2);
obj   // {a: null, b: 1, c: undefined}
(2) 同名屬性的替換

如果目標對象與源對象中的屬性具有相同的鍵,則目標對象屬性將被源中的屬性覆蓋。後來的源的屬性將類似地覆蓋早先的屬性。

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
obj    // { a: 1, b: 2, c: 3 }
(3) 淺拷貝

Object.assign() 方法實行的是淺拷貝,而不是深拷貝。拷貝的是屬性值。假如源對象的屬性值是一個指向對象的引用,它也只拷貝那個引用值。

var obj1 = { a: 0 , b: { c: 0 } };
var obj2 = Object.assign({}, obj1);
obj2   // { a: 0, b: { c: 0 } };

obj2.b.c = 3;
obj1   // { a: 0, b: { c: 3 } };
obj2   // { a: 0, b: { c: 3 } };
(4) 數組的處理

Object.assign() 可以用來處理數組,但是會把數組視爲鍵值爲數組下標的對象來合併,然而最終的返回形式也是數組。

Object.assign([1, 2, 3], [4, 5])  // [4, 5, 3]

Object.assign({0:1,1:2,2:3},{0:4,1:5})  // {0: 4, 1: 5, 2: 3}
(5) 存取器屬性的處理

Object.assign() 如果遇到存取器定義的屬性,會只拷貝值。

var obj = {
  foo: 1,
  get bar() { return 2; }
};

var copy = Object.assign({}, obj); 
copy  // { foo: 1, bar: 2 }

因此必須使用 Object.getOwnPropertyDescriptors() 方法配合 Object.defineProperties() 方法,就可以實現正確拷貝。但僅限於可拷貝 gettersetter ,對於屬性的引用類型還是屬於淺拷貝。


var obj = {
  foo: { a : 0 },
  get bar() { return 2; }
};
var target = Object.defineProperties({},
  Object.getOwnPropertyDescriptors(obj)
);
Object.getOwnPropertyDescriptor(target, 'bar')
// { get : ƒ bar(),
   set : undefined,
   enumerable : true, 
   configurable : true }
   
obj.foo.a = 6
target.foo.a   // 6

常見用途

1. 爲對象添加屬性

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

上面方法通過 Object.assign() 方法,將 x 屬性和 y 屬性添加到 Point 類的對象實例。

2. 爲對象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) { ··· },
  anotherMethod() { ··· }
});

// 等同於下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) { ··· };
SomeClass.prototype.anotherMethod = function () { ··· };

3. 淺克隆對象

let obj = {a:5};
function clone(origin) {
  return Object.assign({}, origin);
}
let aaa = clone(obj);  // {a:5}

不過,採用這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。如果想要保持繼承鏈,可以採用下面的代碼。

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

4. 合併多個對象

let merge = (target, ...sources) => Object.assign(target, ...sources);

如果希望合併後返回一個新對象,可以改寫上面函數,對一個空對象合併。

let merge = (...sources) => Object.assign({}, ...sources);

5. 爲屬性指定默認值

const DEFAULTS = {
  a: 0,
  b: 'ccc'
};

function copy(options) {
  options = Object.assign({}, DEFAULTS, options);
  // ...
}

注意,由於存在淺拷貝的問題,DEFAULTS對象和options對象的所有屬性的值,最好都是簡單類型,不要指向另一個對象。否則,DEFAULTS對象的該屬性很可能不起作用。

參考鏈接:Object.assign()

深拷貝

1. JSON.parse(JSON.stringify(obj))

var obj1 = { a: 0 , b: { c: 0}};
var obj2 = JSON.parse(JSON.stringify(obj1));
obj1.b.c = 4;
obj2    // { a: 0, b: { c: 0}}

但由於 JSON 的侷限性,該方法也不是萬能的。比如,如果對象的屬性是 undefined、函數、symbolXML 對象,該屬性會被 JSON.stringify() 過濾掉,導致拷貝時會缺少屬性。

let obj = {
  name:'dora',
  sayHello:function(){ console.log('Hello World'); }
}

let cloneObj = JSON.parse(JSON.stringify(obj));
console.log(cloneObj); // {name: "dora"}

2. 利用遞歸對每一層都重新創建對象並賦值從而實現深拷貝

function deepClone(source){
  let targetObj = source.constructor === Array ? [] : {}; 
  for(let keys in source){
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{
        targetObj[keys] = source[keys];
      }
    }
  }
  return targetObj;
}

let obj = {
  a:{b:1,c:2},
  sayHello:function(){ console.log('Hello World'); }
}
let cloneObj = deepClone(obj);

obj.a.b = 4

obj       // {a:{b: 4, c: 2},sayHello:ƒ ()}
cloneObj  // {a:{b: 1, c: 2},sayHello:ƒ ()}









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章