JavaScript 物件複製方法比較
0 |
複製物件是 JavaScript 的實用技巧之一,十幾前我就學會用 jQuery.extend() 搞定,常見應用是結合參數預設值及函式傳入的設定值,例如:
const defaults = {
fontSize: '10pt',
color: 'white',
backgroundColor: 'red'
}
funtion showSomething(options) {
const finalOptions = $.extended({}, defaults, options);
//...略...
something.style.fontSize = finalOptions.fontSize;
something.style.color = finalOptions.color;
something.style.backgroundColor = finalOptions.backgroundColor;
//...略...
}
靠 jQuery.extend() 再戰十年也是沒問題的,不過最近剛解封,我對 JavaScript 好奇心正盛,花了點時間研究複製物件這件事。
開始前先說物件複製常要考慮的事:
- 除了複製屬性 (Property) 外,函式 (Function/Method) 也要複製嗎?
- Shallow Copy (淺層複製) 還是 Deep Copy (深層複製) ?
當屬性型別為物件(Non-Primitive Type,可想成 C# 的 Reference Type)時,Shallow Copy 複製屬性會指向原物件、Deep Copy 則是為該物件產生副本,事後修改原物件不會對 Deep Copy 物件產生影響。
參考網路文章,我整理了幾種 JavaScript 複製物件方法:
- Spread 法 (Shallow Copy,會複製函式)
寫成{ ...src }
,相當於將 src 屬性展開傳入,等於{ src.prop1, src.prop2, src.prop3,... }
,套用前篇文章提到的 Shorthand Property Name,巧妙地讓新物件具有跟原物件一樣的屬性與方法。
Spread in object literals 語法,Safari 要 11.3 才支援,Deno 不支援,需留意瀏覽器支援性 - Object.assign 法 (Shallow Copy,會複製函式)
寫成Object.assign({}, src)
- JSON 大絕 (Deep Copy,不複製函式)
JSON.parse(JSON.stringify(src))
,要留意還原失真問題(例如:Date() 序列化再還原會變成字串JSON.parse(JSON.stringify(new Date()))
) - jQuery.extend (Shallow Copy,會複製函式)
寫成$.extend({}, src)
,還可一次合併多個$.extend({}, src1, src2)
- Lodash .clone() & .cloneDeep() (有 Shallow Copy 及 Deep Copy 兩種,會複製函式)
寫成_.clone(src)
或_.cloneDeep()
最後,用彙整範例結束這回合:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { display: flex; font-size: 9pt; }
.tab { width: 320px;}
table { border-collapse: collapse; border-spacing: 0; }
td { padding: 3px 6px; border: 1px solid gray; }
thead td, tbody td:first-child { text-align: left; background-color: #eee; }
tbody td { text-align: center; }
</style>
</head>
<body>
<div class="tab">
<table>
<thead>
<tr>
<td>Method</td>
<td>Deep/<br />Shallow</td>
<td>Functions<br />Included?</td>
<td>Date Type<br /> Lost?</td>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<pre></pre>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
const instance = {
status: "before"
};
const src = {
i: 123,
a: [1, 2, 3],
s: 'String',
d: new Date(2012, 11, 21),
hi() { console.log('Hi'); },
obj: { p: 'test' },
ins: instance
};
const bySpread = { ...src };
const byAssign = Object.assign({}, src);
const byJson = JSON.parse(JSON.stringify(src));
const byJQuery = $.extend({}, src);
const byLodashShallow = _.clone(src);
const byLodashDeep = _.cloneDeep(src);
instance.status = 'after';
src.hi();
const res = [];
const logs = [];
const check = (o) => {
const varName = Object.keys(o)[0];
const obj = o[varName];
res.push([
varName,
obj.ins.status == 'before' ? 'Deep' : 'Shallow',
typeof (obj.hi) == 'function' ? 'Y' : 'N',
obj.d instanceof Date ? 'N' : 'Y'
]);
logs.push(`=== ${varName} ===`);
Object.keys(obj).forEach(propName =>
logs.push(` * ${propName}[${typeof (obj[propName])}] = ${JSON.stringify(obj[propName])}`));
}
check({ bySpread });
check({ byAssign });
check({ byJson });
check({ byLodashShallow });
check({ byLodashDeep });
check({ byJQuery });
$('pre').text(logs.join('\n'));
$('tbody').html(
res.map(a => `<tr>${a.map(e => `<td>${e}</td>`).join()}</tr>`).join('\n')
);
</script>
</body>
</html>
其中的 check() 有點意思,特別說一下。我用了一個小技巧去抓變數名稱,JavaScript 不像 C# 有 nameof(varName) 可將變數名稱轉成字串,但再次借用前幾天學到的 Shorthand Property Name 宣告物件 ,透過 Object.keys(o) 取屬性名稱便能得到 "varName",還蠻有趣的。而 check() 函式會進行以下檢查:
- 複製物件後,instance.status 從 'before' 被改為 'after',若為 Deep Copy,ins.status 應為 before;若為 after 即為 Shallow Copy。
- 用 typeof 檢查複製物件的 hi() 是否為 function 檢查函式有無被複製。
- 用 .d instanceof Date 檢查 d 屬性型別是否失真。
六種做法的處理特性如下表:
Method | Deep/Shallow | Functions? | Date Type Lost? |
---|---|---|---|
bySpread | Shallow | Y | N |
byAssign | Shallow | Y | N |
byJson | Deep | N | Y |
byLodashShallow | Shallow | Y | N |
byLodashDeep | Deep | Y | N |
byJQuery | Shallow | Y | N |
附上完整測試結果:
【參考資料】
- 3 Ways to Clone Objects in JavaScript by samanthamming
- 深度了解淺拷貝(Shallow Copy) VS 深度拷貝(Deep Copy)得部分 by Nick Huang
[2022-05-07 補充] PO 文後再獲新知(感謝莊志弘與張清忠兩位先進不約而同分享),Chrome 98+ 新增 structuredClone() API,主要用於資料傳輸物件之複製,具有一些特異功能如:能處理循環參照、複製後讓原物件無法使用... 等。但其能複製的型別有限,若包含不支援型別(例如函式)會出錯,例如:Failed to execute 'structuredClone' on 'Window': hi() { console.log('Hi'); } could not be cloned.
。
This article summarizes common object cloning methods in JavaScript.
Comments
Be the first to post a comment