這就得先來談談 Javascript 的基本型別(Primitive Type) vs 物件(Object)
Data Type分別為:
- Primitive Type: Number, String, Boolean, Null, Undefined
- Object: Object, Array, Function, Date, Regx
- 基本型別是 by Value
- 物件是by reference
如果是 Primitive Type
如果是 By Value 的方式,不會改變原本的。
- var copyMe = 1234;
- function copy(o) {
- var copyObj = o;
- // 直接給值
- copyObj = 55;
- console.log(o); //1234 不變
- console.log(copyObj);//55
- }
- copy(copyMe);
假如有個需要複製的物件(接下來都用這個來舉例)
以下舉例
- var copyThis = {a:11, b:21, c:33};
當直接給值55的時候,又重新定義了 copyObj,所以不影響;
- function copy1(o) {
- var copyObj = o;
- // 直接給值
- copyObj = 55;
- console.log(o); //{a:11, b:21, c:33}
- console.log(copyObj);//55
- //-----------------------------------------------
- copyObj2 = o;
- copyObj2.a = 60;
- console.log(o); //{a:60, b:21, c:33} 被改變
- console.log(copyObj2);//{a:60, b:21, c:33}
- }
- copy1(copyThis);
但是在下半段,修改 copyObj2.a 連動到 o.a,因為他們指向同一個節點位置,所以原本的也被改變了。
So...
- 淺拷貝:就是物件指向某個指標(Node),但還是共用同一塊記憶體。
- 深拷貝:就是另外建立新的物件,有同樣的內容,且不共用記憶體。
但還是有些例外
object literal 的方式指定物件的值,那麼就會是 by value
- function copy2(o) {
- var copyObj = o;
- // 直接給值
- o = {a:33, b:55, c:77};
- console.log(o); //{a:33, b:55, c:77} 被改變
- console.log(copyObj);//{a:11, b:21, c:33} 不變
- }
- copy2(copyThis);
咦!什麼是 object literal?
請參考 [筆記] 談談JavaScript中的物件建立(Object) - Part 2 | 利用大括號{}建立物件
如果不更動到原本的呢?
雖然這樣可以避免,但這樣一個個列出太麻煩,且這樣不是deep copy
- function copy3(o) {
- var copyObj = {};
- copyObj = {
- a:o.a,
- b:o.b,
- c:o.c
- };
- copyObj.a = 14;
- console.log(o); // {a:11, b:21, c:33}
- console.log(copyObj); //{a:14, b:21, c:33}
- }
- copy3(copyThis);
那麼,如果使用
Object.create()
??在這邊請注意,可能發生以下狀況:當
- function copy6(o) {
- var copyObj = Object.create(o);
- copyObj.a = 77;
- copyObj.b = 66;
- console.log(o); // {a:11, b:21, c:33}
- console.log(copyObj.a); // {a: 77, b: 66, c: 33}
- }
- copy6(copyThis);
Object.create()
遇上 Array為什麼呢?Javascript Arrays created with Object.create - not real Arrays?
- var copyArr = [{a: 11, b: 21, c: 33}];
- function copy7(o) {
- var copyObj = Object.create(o);
- copyObj[0].a = 88;
- console.log(o); // {a: 88, b: 21, c: 33} //被改變了...
- console.log(copyObj); // a: 88, b: 21, c: 33}
- }
- copy7(copyArr);
總結就是,
Object.create()
而言,是建立一個Array屬性的「物件」,這又回到 by reference 的問題了。恩⋯⋯那如果反過來,
Array.form()
和Object.create()
合併使用?此時發現,原本的不會被改變,但是如果遇到以下狀況⋯⋯
- var copyArr7_1 = [11, 21, 33];
- function copy7_1(o) {
- var copyObj = Array.from(Object.create(o));
- copyObj[0] = 88;
- console.log(o); // [11, 21, 33]
- console.log(copyObj); // [88, 21, 33]
- }
- copy7_1(copyArr7_1);
這時就得探討
- var copyArr7_2 = [{a:11}, {b:21}, {c:33}];
- function copy7_2(o) {
- var copyObj = Array.from(Object.create(o));
- copyObj[0].a = 88;
- console.log(o); // {a: 88, b: 21, c: 33} //被改變了...
- console.log(copyObj); // {a: 88, b: 21, c: 33}
- }
- copy7_2(copyArr7_2);
Array.form()
,解法:先
JSON.stringify
轉成 JSON 格式,再JSON.parse
解開。解法:使用 ES6 新的函式 Object.assign()
- function copy4(o) {
- var copyObj = JSON.parse(JSON.stringify(o));
- copyObj.a = 14;
- console.log(o); // {a:11, b:21, c:33}
- console.log(copyObj); // {a: 14, b: 21, c: 33}
- }
- copy4(copyThis);
- function copy5(o) {
- var copyObj = Object.assign({},o);
- copyObj.a = 14;
- console.log(o); // {a:11, b:21, c:33}
- console.log(copyObj); // {a: 14, b: 21, c: 33}
- }
- copy5(copyThis);
Object.assign({},o)
,{}
意思是另建立一個空物件,再將o屬性質複製過去,因此此方法只針對一層的物件有用。可以用在一些簡單的需求。當然,運用 library 也可以快速達到 deep copy 效果:
- lodash 的
cloneDeep()
- jQuery 的
extend()
最後,放上小小的實作⋯⋯
- //訂單結帳
- var shoppingCar = [
- {usr_id:'AB123',ord_id:'NA20171212',items:[
- {item:'Pants', it_price:'23'},
- {item:'Shirt', it_price:'21'},
- {item:'Top', it_price:'15'},
- function contactMethod () {
- return 'AAAAA';
- }]
- },
- {name:'Aero',mobile:'+8869000000',add:'Taipei, Taiwan'}
- ];
- function checkout(order, rate) {
- var output = {};
- for (var key in order) {
- var o = order[key];
- if (key === "it_price"){
- o = discount(rate, o);
- }
- output[key] = o;
- if (typeof o === 'function') {
- output[key] = new Function("return "+o.toString());
- }
- if (typeof o === 'object') {
- output[key] = checkout(o, rate);
- }
- }
- return output;
- }
- function discount(rate, price) {
- var output = price * rate/100;
- return output;
- }
- var myOrder = checkout(shoppingCar, 70);
- console.log(myOrder);
- console.log(shoppingCar);
參考資料
https://pjchender.blogspot.tw/2016/03/javascriptby-referenceby-value.html
https://www.codementor.io/avijitgupta/deep-copying-in-js-7x6q8vh5d
http://larry850806.github.io/2016/09/20/shallow-vs-deep-copy/
https://github.com/wengjq/Blog/issues/3