點燈坊

學而時習之,不亦悅乎

如何 Clone Object ?

Sam Xiao's Avatar 2019-08-16

= 只能 Copy Object 的 Reference,而非真正 Clone Object,本文整理出 4 種方式,並詳細分析其特色。

Version

macOS Mojave 10.14.5
VS Code 1.37.0
Quokka 1.0.240
ECMAScript 2015
Ramda 0.26.1

Assignment Operator

import { equals } from 'ramda';

let obj1 = { name: 'Sam' };
let obj2 = obj1;

obj1 === obj2; // ?
equals(obj1, obj2); // ?

直覺會使用 = assignment operator。

clone000

使用 === 判斷為 true,可見 obj1obj2 的 reference 相同,因此為 copy reference,並非 clone object。

使用 Ramda 的 equals() 判斷為 true,由於 obj1obj2 的 reference 相同,value 當然相同。

Object.assign()

import { equals } from 'ramda';

let obj1 = { name: 'Sam' };
let obj2 = Object.assign({}, obj1);

obj1 === obj2; // ?
equals(obj1, obj2); // ?

在 ES5 若要 clone object,必須使用 Object.assign()

Object.assign(target, …sources)
將 source object merge 到 target object

target:target object

...sources:source object,可包含多個 object

Object.assign() 原本是 merge object,所以第一個 argument 為 target object,若 target object 為 {},其行為剛好等於 clone object。

clone001

JSON

import { equals } from 'ramda';

let obj1 = { name: 'Sam' };
let obj2 = JSON.parse(JSON.stringify(obj1));

obj1 === obj2; // ?
equals(obj1, obj2); // ?

ES5 也可使用 JSON.stringify() 將 object 轉成 JSON string 後,再使用 JSON.parse() 轉成 object,由於這兩個 function 皆是 pure function,剛好實現了 clone object。

clone002

Spread Operator

import { equals } from 'ramda';

let obj1 = { name: 'Sam' };
let obj2 = { ...obj1 };

obj1 === obj2; // ?
equals(obj1, obj2); // ?

ES6 亦引進了 ... spread operator,可將 object property 展開,若放在 {} 內,也能實現 clone object。

clone003

Ramda

import { clone } from 'ramda';

let obj1 = {
  id: 1,
  name: {
    first: 'Sam',
    last: 'Xiao'
  }
};

let obj2 = {...obj1}; // ?
let obj3 = clone(obj1); // ?

obj2 === obj1; // ?
obj3 === obj1; // ?

obj1.name === obj2.name; // ?
obj1.name === obj3.name; // ?

無論是 Object.assign()JSON.stringify() / JSON.parse() 或 spread operator,都只能對 object 做 shallow clone,第二層之後 object 為 copy reference。

obj1name 包含 nested object,其中 obj2 使用 ...,而 obj3 使用 Ramda 的 clone()

clone004

無論使用 ...clone()obj2obj3 的 reference 皆與 obj1 不同。

  • arr2.nameobj1.name 的 reference 相同,顯然 ... 是 shallow clone
  • arr3.nameobj1.name 的 reference 不同,顯然 clone() deep clone

Conclusion

  • 若要 shallow clone,則 ... spread operator 是可讀性最高寫法;若要 deep clone,則要使用 Ramda 的 clone()

Reference

Samantha Ming, 3 Ways to Clone Objects in JavaScript
MDN, object.assign()
MDN, JSON.stringify()
MDN, JSON.parse()
Ramda, clone