這就得先來談談 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