物件的合併與拷貝(又稱複製或克隆)是前端們平時工作中繞不開的基本操作,使用場景非常多。也許你已經有了自己用慣了的工具方法,但是對於這個話題,你確定自己已經完全瞭解了嗎?
合併與克隆的關係
拷貝可以認為是一種特殊情況下的合併:將一個空物件 {}
作為目標,與一個非空物件合併。
constructor
相同,一個 Person
類的例項被拷貝後應該還是一個 Person
,不能變成 Dog
,更不能變成一個不知道是什麼東西的 Object
。此外,合併和拷貝在方法呼叫上也有差別。合併一般要求支援多個源物件向目標物件合併,而拷貝的源物件只有一個。
常見的合併與拷貝的方法
JSON.parse(JSON.stringify() )
來實現物件複製。ES5 中增加了原生的 Object.assign
來實現合併。而利用 ES6 中的擴充套件運算子,呼叫形如 {…x, …y}
的宣告,也能實現物件的合併。除了這些原生的合併拷貝方法,我還找了jQuery@3.2.1,underscore@1.8.3,lodash@4.16.1
三個大名鼎鼎庫中的相關方法。我們接下來將以他們作為例子,詳細從合併與拷貝方法的各個維度上來分析。
說明:jQuery.extend
方法有很多呼叫方式,既可以拷貝又可以合併,所以在兩個列表中都出現。另外,lodash
和 underscore
都使用下劃線_,這裡根據“先來後到”的順序,用 _ 指代underscore
,用 l 指代 lodash
。
JSON.parse(JSON.stringify())
$.extend
_.clone
l.clone
l.cloneDeep
l.cloneWith
l.cloneDeepWith
{…x, …y}
Object.assign
$.extend
_.extend
_.extendOwn
l.assign
l.assignIn
l.assignWith
l.assignInWith
l.merge
l.mergeWith
拷貝方法分析
對於依賴 JSON 來拷貝物件的 JSON.parse(JSON.stringify())
方法來說,undefined
和 function
型別的屬性會被忽略,而 Date
型別的屬性則會被轉換為字串,這可能不是我們想要的。
2.是否能夠正確處理 constructor?
Person
不能變為 Dog
,也不能變為 Object
。目標物件應該保留源物件的 constructor
。3.是否是深拷貝?
4.是否支援 customizer?
customizer
是指一個處理方法,允許使用者定製拷貝中的處理過程,其作用類似 Array
系列方法中的遍歷處理函式。一開始我也沒想到這個維度,還是在研究 lodash
相關方法的時候才看到的。不得不說,這是一個很有用的特性。
function customizer(value) {
if (_.isElement(value)) {
return value.cloneNode(false);
}
}
var el = _.cloneWith(document.body, customizer);複製程式碼
方法
|
是否支援處理特殊型別
|
是否能夠正確處理constructor
|
是否是深拷貝
|
是否支援customizer
|
JSON.parse(JSON.stringify())
|
否
|
否
|
是
|
否
|
$.extend
|
是
|
否
|
支援(第一個引數為true)
|
否
|
_.clone
|
是
|
否
|
否
|
否
|
l.clone
|
是
|
是
|
否
|
否
|
l.cloneDeep
|
是
|
是
|
是
|
否
|
l.cloneWith
|
是
|
是
|
否
|
是
|
l.cloneDeepWith
|
是
|
是
|
是
|
是
|
underscore
的clone
方法不支援深拷貝,比較弱。jquery
的extend
方法預設不使用深拷貝,但當第一個引數傳入 true 時則使用深拷貝來處理。- lodash 提供了4個
clone
相關方法。只有 lodash 的 clone 方法正確處理了constructor
,而customizer
也只有lodash
一家獨有(兩個with 方法)。
合併方法分析:
1.原型屬性是否參與合併?
原型屬性參與合併時,源物件原型上的屬性會被作為目標物件上的普通屬性。如:
function Foo() {
this.a = 1;
}
Foo.prototype.b = 2;
let x = new Foo();
assign({}, x);
// {a: 1, b: 2}複製程式碼
字面意思。值 為undefined 的屬性是否參與合併。
3.是否遞迴合併?
首先確認一點,所有的合併操作都不會是“淺”的,都不會直接把引用地址賦給目標物件。但在此基礎上,又有不同的合併策略。比如:
let x = {a: {m: 1, n: 2}};
let y = {a: {m: 2, o: 3}};
assign(x ,y);
// 非遞迴合併
// {a: {m: 2, o: 3}}
// 遞迴合併
// {a: {m: 2 , n: 2, o: 3}}複製程式碼
4.是否支援 customizer?
方法
|
原型屬性是否參與合併
|
undefined是否參與合併
|
是否遞迴合併
|
是否支援customizer
|
{…x, …y}
|
否
|
是
|
否
|
否
|
Object.assign
|
否
|
是
|
否
|
否
|
jQuery.extend
|
否
|
否
|
支援(第一個引數為true)
|
否
|
_.extend
|
是
|
是
|
否
|
否
|
_.extendOwn
|
否
|
是
|
否
|
否
|
l.assign
|
否
|
是
|
否
|
否
|
l.assignIn
|
是
|
是
|
否
|
否
|
l.assignWith
|
否
|
是
|
否
|
是
|
l.assignInWith
|
是
|
是
|
否
|
是
|
l.merge
|
否
|
否
|
是
|
否
|
l.mergeWith
|
否
|
否
|
是
|
是
|
- 原生方法中使用
Object.assign
方法和使用擴充套件操作符完全一樣。 - 除了lodash 的
merge
,其餘方法都不支援遞迴合併。 - 除了lodash 的
merge
,其餘方法undefined
都參與合併。 - 除了lodash 的三個
with
方法,其餘方法都不支援customizer
。
根據上面的兩張表,讀者可以自行選擇合適的合併與拷貝方法了。如果有其他方法,也可以用這些維度來進行分析。
最後,歡迎拍磚~!