在這篇文章中,我將會講到幾種在js中複製物件的方式,我們將會關注到淺複製和深複製。
在開始之前,值得一提的是一些基礎知識:js中的物件只是對記憶體中某個位置的引用。這些引用是可以更改的。即他們可以被重新分配。從而,簡單的複製引用的操作在結果上僅僅是將兩個引用指向了記憶體中的同一位置。
var foo =
console.
log(foo.a)
// abc
var bar = foo
console.
log(bar.a)
// abc
foo.a =
'yo foo'
console.
log(foo.a)
// yo foo
console.
log(bar.a)
// yo foo
bar.a =
'whatup bar?'
console.
log(foo.a)
// whatup bar?
console.
log(bar.a)
// whatup bar?
正如你從上面例子中看到的,foo和bar都反映了同乙個物件的變化。從而,在js中複製物件需要小心,具體取決於您的用例。
如果你的物件的屬性的型別僅僅只是值型別(譯者注:基本型別)的。你可以使用擴充套件運算子語法或者object.assign(...)
var obj =
var copy =
// object
var obj =
var copy = object.
assign
(, obj)
// object
請注意,上述兩種方法都可用於將屬性值從多個源物件複製到目標物件:
var obj1 =
var obj2 =
var copyspread =
// object
var copyassign = object.
assign
(, obj1, obj2)
// object
事實上,上述方法的問題在於物件的屬性如果是乙個物件,則只會複製該屬性物件在記憶體中的引用。即它相當於var bar = foo
,如同第乙個**例子:
var foo =
}var copy =
copy.a =
1copyb.c.=2
console.
dir(foo)
// }
console.
dir(copy)
// }
為了對物件進行深複製操作,乙個潛在的解決方案是序列化物件為乙個字串,然後反序列化,生成乙個新物件:
var obj =
}var copy =
json
.parse
(json
.stringify
(obj)
)
不幸的是,這個方法僅僅適用於當源物件包含可序列化的值型別並且沒有迴圈引用的情況。不能序列化的值的型別,比如date
物件,即使它在字串化上以iso格式列印。json.parse
僅僅會將它解釋為乙個字串,而不是date
物件。
對於更複雜的物件,可以使用更新的html5的structured clone轉殖演算法。不幸的是,在撰寫本文時,它仍侷限於某些內建型別,但它支援的內容型別比json.parse
更多。比如:date
、regexp
、map
、set
、blob
、filelist
、imagedata
、稀疏和型別化陣列。它還在轉殖物件中保留了引用關係。允許它支援不適用於上述序列化方法的迴圈和遞迴結構。
當前還沒有直接呼叫結構化轉殖演算法的方法,但一些新的瀏覽器特性可以被用來間接使用這個方法。從而,我們會得到一些可能用於深度複製物件的變通方法
使用messagechannel
:這背後的想法是利用messagechannel
通訊功能使用的序列化演算法。這個功能是基於事件的,因此獲取轉殖結果是乙個非同步的操作。
class
structruedcloner})
=>
this
.outport_.
start()
}cloneasync
(value))}
)}}const structuredcloneasync = window.structuredcloneasync =
structuredcloner.prototype.cloneasync.
bind
(new
structuredcloner
)const main =
async()
=>
// 譯者注釋:新增一些json方法不能解釋的物件
original.self = original // 譯者注釋:新增迴圈引用
const clone =
await
structuredcloneasync
(original)
// 不同的物件
console.
assert
(original !== clone)
console.
assert
(original.date !== clone.date)
// 迴圈
console.
assert
(original.self === original)
console.
assert
(clone.self === clone)
// 等價值
console.
assert
(original.number === clone.number)
console.
assert
(number
(original.date)
===number
(clone.date))
console.
log(
'assertions complete.')}
main
()
使用history
api:history.pushstate()
和history.replacestate()
兩個方法會建立它們第乙個引數的結構化物件。注意這個方法是同步的,操縱瀏覽器歷史記錄不是乙個快速的操作並且反覆呼叫此方法可能導致瀏覽器無響應。
const
structureclone
= obj =>
使用notification
api:當建立乙個新的提醒(譯者注:notification),建構函式會從它所關聯的資料中建立乙份結構化的轉殖副本。注意,這麼做瀏覽器會嘗試將提醒顯示給使用者。但是這將會靜默失敗。除非應用已經請求到顯示提醒的許可權。萬一許可權存在,提醒會立即關閉。
const
structuredclone
= obj =>
) n.onshow = n.close.
bind
(n)return n.data
}
在nodejs的8.0.0版本中,它提供了乙個序列化的api,它是相容結構化轉殖的。注意:這個api在本文撰寫時(譯者注:原文發表於2018.11.1)還是標記為實驗性的:
const v8 =
require
('v8'
)const buf = v8.
serialize()
const cloned = v8.
deserialize
(buf)
cloned.b.
getmonth
()
對於版本低於8.0.0或者更穩定的實現,一種方法是:可以使用lodash的clonedeep
方法。該方法也基於結構化轉殖演算法
總而言之,在js中複製物件最佳的演算法是嚴重依賴於你所複製物件的上下文和型別的。而lodash是通用深複製函式最安全的選擇。也許你會給出更高效的實現呢。下面是乙個對date
物件也起效的深複製的函式:
function
deepclone
(obj)
// 處理陣列
if(array instanceof
array
)return copy
}// 處理函式
if(obj instanceof
function
)return copy
}// 處理物件
if(obj instance object)
for(
var attr in obj)
return copy
}throw
newerror
("unable to copy obj as type isn't suported"
+ obj.constructor.name)
}
就個人而言,我期待能夠在任何地方使用結構化轉殖,最後讓這個問題得到休息,快樂的轉殖:) js物件複製
淺複製 var obj var obj1 var obj2 object.assign obj var obj var obj1 var obj2 object.assign obj obj.c.push 2 var obj var copyobj json parse json stringify...
js深度複製物件
js在處理複雜資料的時候,可能會涉及到引用型別的物件或者陣列的copy問題,下面是兩種複製物件或陣列的方法 一 利用jquery自帶的方法,呼叫簡單方便 淺層複製 只複製object根級的各個值 var newobject jquery.extend oldobject 深層複製 會複製整個 包括根...
關於js物件的複製
兩個物件的直接賦值是淺複製,只是將被賦值物件 listb 指向了賦值物件 lista 的位址 所以兩者的值都是一樣的 let lista let listb lista 如果像以下 lista物件的屬性指向了lista物件所儲存的位址,那麼就會出線無限的 巢狀 由於兩個物件指向的位址依舊沒有變化,所...