這就得先來談談 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
var copyMe = 1234; function copy(o) { var copyObj = o; // 直接給值 copyObj = 55; console.log(o); //1234 不變 console.log(copyObj);//55 } copy(copyMe);如果是 By Value 的方式,不會改變原本的。
假如有個需要複製的物件(接下來都用這個來舉例)
var copyThis = {a:11, b:21, c:33};以下舉例
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);當直接給值55的時候,又重新定義了 copyObj,所以不影響;
但是在下半段,修改 copyObj2.a 連動到 o.a,因為他們指向同一個節點位置,所以原本的也被改變了。
So...
- 淺拷貝:就是物件指向某個指標(Node),但還是共用同一塊記憶體。
- 深拷貝:就是另外建立新的物件,有同樣的內容,且不共用記憶體。
但還是有些例外
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 的方式指定物件的值,那麼就會是 by value
咦!什麼是 object literal?
請參考 [筆記] 談談JavaScript中的物件建立(Object) - Part 2 | 利用大括號{}建立物件
如果不更動到原本的呢?
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);雖然這樣可以避免,但這樣一個個列出太麻煩,且這樣不是deep copy
那麼,如果使用
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()
遇上 Arrayvar 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);為什麼呢?Javascript Arrays created with Object.create - not real Arrays?
總結就是,
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
解開。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);解法:使用 ES6 新的函式 Object.assign()
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