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; }