本節內容我們繼續探討關於ES2015的一些新的內容,Object.assign函數的使用,使用該函數我們可以快速的復制一個或者多個對象到目標對象中,本文內容涉及es6,es7相關的對象復制的內容,以及一些es5的替代方案的介紹。
函數原型
首先看一下函數的定義: 函數參數為一個目標對象(該對象作為最終的返回值),源對象(此處可以為任意多個)。通過調用該函數可以拷貝所有可被枚舉的自有屬性值到目標對象中。
Object.assign(target, ...sources)
這里我們需要強調的三點是:
- 可被枚舉的屬性
- 自有屬性
- string或者Symbol類型是可以被直接分配的
拷貝過程中將調用源對象的getter方法,並在target對象上使用setter方法實現目標對象的拷貝。
函數實例
這里我們通過幾個MDN上的例子來介紹一下使用方法:
實例一
我們參考上面的原型函數說明即可知道其最開始的o1因為設置為target,則調用其setter方法設置了其他對象的屬性到自身。
var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, target object itself is changed.
實例二
我們自定義了一些對象,這些對象有一些包含了不可枚舉的屬性,另外注意使用 Object.defineProperty 初始化的對象默認是不可枚舉的屬性。對於可枚舉的對象我們可以直接使用Object.keys()獲得,或者使用for-in循環遍歷出來.
對於不可枚舉的屬性,使用Object.assign的時候將被自動忽略。
var obj = Object.create({ foo: 1 }, { // foo is an inherit property. bar: { value: 2 // bar is a non-enumerable property. }, baz: { value: 3, enumerable: true // baz is an own enumerable property. } }); var copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }
實例三
對於只讀的屬性,當分配新的對象覆蓋他的時候,將拋出異常:
var target = Object.defineProperty({}, 'foo', { value: 1, writable: false }); Object.assign(target, { bar: 2 }) //{bar: 2, foo: 1} Object.assign(target, { foo: 2 }) //Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'(…)
Polyfill
這里我們簡單的看下如何實現es5版本的Object.assign:
實現步驟:
- 判斷是否原生支持該函數,如果不存在的話創建一個立即執行函數,該函數將創建一個assign函數綁定到Object上。
- 判斷參數是否正確(目的對象不能為空,我們可以直接設置{}傳遞進去,但必須設置該值)
- 使用Object在原有的對象基礎上返回該對象,並保存為out
- 使用for…in循環遍歷出所有的可枚舉的自有對象。並復制給新的目標對象(hasOwnProperty返回非原型鏈上的屬性)
源碼如下:
if (typeof Object.assign != 'function') { (function () { Object.assign = function (target) { 'use strict'; if (target === undefined || target === null) { throw new TypeError('Cannot convert undefined or null to object'); } var output = Object(target); for (var index = 1; index < arguments.length; index++) { var source = arguments[index]; if (source !== undefined && source !== null) { for (var nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; }; })(); }
擴展內容
1.深度復制
當我們調用下面的函數的時候,由於Object.assign將覆蓋之前的內容,所以並不能完全的做到融合對象,而是全部替換掉,所以返回的對象內容將變成最后一個值; {a: {c: 3}
Object.assign({a: {b: 0}}, {a: {b: 1, c: 2}}, {a: {c: 3}});
如何深層次的融合對象,比如我們期望的輸出結果為:
{a:{b:1,c:3}}
這樣我們必須實現自己的算法來完成深層復制了,不過github上已經有很多好的解決方案,比如deep-merge 通過遞歸的方式逐層的去調用assign函數。
2.ES2016實現
在es7中我們使用rest屬性可以捕獲所有剩余的對象內容比如下面的例子(可使用babel-repl頁面測試,瀏覽器一般尚未支持):
let { fname, lname, ...rest } = { fname: "Hemanth", lname: "HM", location: "Earth", type: "Human" }; fname; //"Hemanth" lname; //"HM" rest; // {location: "Earth", type: "Human"}
這樣我們就可以使用該特性來實現assign函數
let oldObj1={a:"a",b:{b1:"b1"}} let oldObj2={a:"a1",b:{b2:"b2"},c:"c"} let newObject={...oldObj1,...oldObj2}; console.log(newObject) {"a":"a1","b":{"b2":"b2"},"c":"c"}
不過仍舊只是淺層的替換,並沒有實現深層次的合並。