此篇會介紹在幾種在 JS 實作淺拷貝、深拷貝的方法。
科普
快速科普一下什麼是傳值(Pass by value)、傳址(Pass by reference)
依照資料型別來決定傳遞行為是 Pass by value 或 Pass by reference。
基本型別 (Primitive Type)
就是傳值(value),會是分別獨立的記憶體空間。(也稱深拷貝)
物件型別 (Object Type)
就是傳址(address),會指向同一個記憶體空間。(也稱淺拷貝)
科普就到這邊,下方會用實際範例來演釋實作淺拷貝、深拷貝的方法。
淺拷貝(Shallow Copy)
最多只能複製第一層(淺層),其它層還是指向相同記憶體位置,因此在較深的結構賦予值還是會相互影響。
原生物件型別 1 2 3 4 let obj1 = {name : 'kent' };let obj2 = obj1;console .log (obj1 === obj2); console .log (obj1.name === obj2.name );
展開運算子 1 2 3 4 let obj1 = {name : 'kent' };let obj2 = {...obj1};console .log (obj1 === obj2); console .log (obj1.name === obj2.name );
object assign 1 2 3 4 let obj1 = {name : 'kent' };let obj2 = Object .assign ({},obj1);console .log (obj1 === obj2); console .log (obj1.name === obj2.name );
深拷貝(Deep Copy)
建立新的記憶體空間來複製全部內容(全部深度),因此兩者是獨立的記憶體空間,彼此不會相互影響。
JSON.parse( JSON.stringify( object ) )
JSON.stringify()
:轉成 JSON 字串,無法
轉換為 JSON 字串的屬性則自動 忽略
。
JSON.parse()
:解析 JSON,將 JSON 轉為基本型別 (Primitive Type) 、物件型別 (Object Type) 。
1 2 3 4 5 6 7 8 9 10 11 12 var obj = { name : 'kent' , age : 5 , action : function ( ){ console .log ('function' ) }, religion : undefined , fortune : NaN }; var obj2 = JSON .parse ( JSON .stringify (obj));console .log (obj2);
lodash 套件的 cloneDeep()
clone()
:只複製淺層的值。
cloneDeep()
:透過遞迴的方式複製全部的值。
好處:不會遇到 JSON.stringify/parse
部分值會非預期改變的問題。
1 2 3 4 5 6 7 8 9 import { clone, cloneDeep } from 'lodash' ;let obj1 = { name : 'kent' , age : 5 , }; let obj2 = clone (obj1);let obj3 = cloneDeep (obj1);console .log (obj1.name === obj2.name ); console .log (obj1.name === obj3.name );
三種自製簡單遞迴 function 作法 第一種:
src: JavaScript中的深拷貝與淺拷貝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const deepCopy = (val ) => { let newVal = Array .isArray (val) ? [] : {}; for (let v in val) { if (val.hasOwnProperty (v)) { if ('object' === typeof val[v]) { newVal[v] = deepCopy (val[v]); } else { newVal[v] = val[v]; } } } return newVal; }; const originalData = { firstLayerNum : 10 , obj : { secondLayerNum : 100 , }, }; const clonedData = deepCopy (originalData);console .log (clonedData === originalData); console .log (clonedData.obj .secondLayerNum === originalData.obj .secondLayerNum );
第二種:
src: JS 中的淺拷貝 (Shallow copy) 與深拷貝 (Deep copy) 原理與實作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function deepCopy (inputObject ) { if (typeof inputObject !== 'object' || inputObject === null ) { return inputObject; } const outputObject = Array .isArray (inputObject) ? [] : {}; for (let key in inputObject) { const value = inputObject[key]; outputObject[key] = deepCopyFunction (value); } return outputObject; } const originalData = { firstLayerNum : 10 , obj : { secondLayerNum : 100 , }, }; const clonedData = deepCopy (originalData);console .log (clonedData === originalData); console .log (clonedData.obj .secondLayerNum === originalData.obj .secondLayerNum );
第三種:
src: jsbench example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function deepCopy (obj ) { var copy; if (null == obj || "object" != typeof obj) return obj; if (obj instanceof Date ) { copy = new Date (); copy.setTime (obj.getTime ()); return copy; } if (obj instanceof Array ) { copy = []; for (var i = 0 , len = obj.length ; i < len; i++) { copy[i] = deepCopy (obj[i]); } return copy; } if (obj instanceof Object ) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty (attr)) copy[attr] = deepCopy (obj[attr]); } return copy; } throw new Error ("Unable to copy obj! Its type isn't supported." ); } const originalData = { firstLayerNum : 10 , obj : { secondLayerNum : 100 , }, }; const clonedData = deepCopy (originalData);console .log (clonedData === originalData); console .log (clonedData.obj .secondLayerNum === originalData.obj .secondLayerNum );
陣列延伸閱讀請參考:
以下圖片出自於 StackOverflow How do I correctly clone a JavaScript object? 討論中提到上述淺拷貝、深拷貝實作方法的在三大瀏覽器中的效能比較。
淺拷貝:展開運算子、assign 兩者是差不多的優異
深拷貝:遞迴 function > lodash > JSON
conclusion
從上方效能比較圖看起來,使用 JSON.parse(JSON.stringify( ))
方法效能是比較差的,此外的方法會依照需求做調整。
reference