基本型別及物件型別
JavaScript上所有的東西可以分成兩大類:純值 Primitive Types 和 物件型別 Object Type
基本型別aka純值 (Primitive Types)
- 數字 number
- 字串 string
- 布林值 boolean
- undefined
- null
- 符號 symbol(ES6)
物件型別 (Object Type)
JavaScript 中除了 Primitive Types 以外的東西,全都是物件型別!
傳值 (by value)
基本型別為傳值特性,將純值 b 指向純值 a,當中 a 或 b 進行修改時,不會互相影響。
| 1 | var a = 1; | 
傳參考 (by reference)
物件型別特性是傳參考,將兩個物件互相指向,修改任一物件內部的屬性,都會影響另一個物件,因為這兩個變數都被指向同一個記憶體位置。
- 物件 - 1 
 2
 3
 4
 5
 6- var obj1 = { name: 'dylan', age: 18 }; 
 var obj2 = obj1;
 obj2.age = 21;
 console.log(obj1); // {name: "dylan", age: 21}
 console.log(obj2); // {name: "dylan", age: 21}
- 陣列 - 1 
 2
 3
 4
 5
 6- var arr1 = [1, 2, 3]; 
 var arr2 = arr1;
 arr2[0] = 10;
 console.log(arr1) // [10, 2, 3]
 console.log(arr2) // [10, 2, 3]
如果要避免傳參考,最簡單的方法可以這麼做。由於物件的指向都是傳參考,而純值為傳值,我們可以一一的去指向物件內的純值來避免這個問題,缺點是非常麻煩。
| 1 | var obj1 = { name: 'dylan', age: 18 }; | 
淺拷貝及深拷貝的定義

來源: Stack Overflow
| 淺拷貝 | 深拷貝 | 
|---|---|
| 只是複製 collection structure,而不是 element 。當在指向 第二層物件時會出現傳參考問題 | 整個複製,包含 element 。所以當我們在使用多層物件時,要盡量用 deep copy | 
淺拷貝 (Shallow Copy)
以下舉幾個常見的淺拷貝範例
Array.concat & Array.slice
使用這兩個的結果是一樣的,也是傳統較常見的淺拷貝方式。詳細的用法請參考這篇 JavaScript 陣列處理常用方法
- Array.concat - 1 
 2- var arr1 = [1, 2, 3, {aa: 'aa'}]; 
 var arr2 = arr1.concat();
- Array.slice - 1 
 2- var arr1 = [1, 2, 3, {aa: 'aa'}]; 
 var arr2 = arr1.slice();
看看結果
| 1 | console.log(arr1); // [1, 2, 3, {aa: 'aa'}] | 
試著改變陣列第一層的值,結果並沒有造成傳參考,證明這個拷貝是成功了
| 1 | arr1[0] = 'Dylan'; | 
接著改變陣列中第二層物件內的值,結果發生了傳參考的問題
| 1 | arr2[3].aa = 'bb'; | 
| 1 | console.log(arr1); // ["Dylan", 2, 3, {aa: 'bb'}] | 
Array.from & Spread
這是兩個都是 ES6 新增的,效果和 slice 及 concat 是一樣的,但是可讀性較佳,算是一種語法糖。
- Array.from - 1 
 2- var arr1 = [1, 2, 3, {aa: 'aa'}]; 
 var arr2 = Array.from(arr1);
- Spread - 1 
 2- var arr1 = [1, 2, 3, {aa: 'aa'}]; 
 var arr2 = [...arr1];
看看結果
| 1 | console.log(arr1); // [1, 2, 3, {aa: 'aa'}] | 
由於同上例子是屬於淺拷貝,結果是一樣的,這裡就簡單示範使用方法,不再作後續實驗。
兩者實際使用差異
Array.from和Spread在實際使用上還是有差異的,請參考這篇
| Spread | Array.from | 
|---|---|
| 它不是運算符,僅適用於可迭代( iterable )的陣列 | 也適用於不可迭代的偽陣列( 具有 length 屬性和索引屬性的陣列 ) | 
| 1 | const arrayLikeObject = { 0: 'a', 1: 'b', length: 2 }; | 
結論是,當你想要將某東西轉換成陣列時,推薦使用 Array.from,因為他更好閱讀。而當你想要連接多個陣列時,Spread 可能較適合,如 ['a', 'b', ...someArray, ...someOtherArray]
Object.assign
Object.assign 的可以傳入兩個參數,第一個參數可以是物件或是陣列,第二個參數為要合併的目標,若第一個參數帶入空物件或空陣列就可以達到淺拷貝的效果。
| 1 | var obj1 = { name: 'Dylan', family: { | 
輸出看看,看似結果一樣
| 1 | console.log(obj1); | 

但是由於淺拷貝的特性,第二層以後的物件還是會有傳參考的問題。
| 1 | console.log(obj1 === obj2); // false | 
在來試著改變其中一個物件的第一層純值,結果並不會傳參考。
| 1 | obj2.name = 'Tony'; | 
再來試著改變物件中的第二層物件,結果就不同了,形成傳參考,這也是淺拷貝的問題。
| 1 | obj2.family.dad = 'Walt'; | 

深拷貝 (Deep Copy)
這邊一樣舉一些例子介紹
JSON.stringify 再 JSON.parse
我們可以使用 JSON.stringify 將陣列或物件轉換成字串後,再用 JSON.parse 轉回來。
| 1 | var obj1 = { name: 'Dylan', family: { | 
| 1 | console.log(obj1 === obj2); // false | 
即使修改第二層物件,也不會影響另一個陣列的內容
| 1 | obj2.family.dad = 'Walt'; | 

這樣就是真正的 Deep Copy,也是這篇介紹唯一不需要使用其他函式庫的深拷貝方式,非常方便,但缺點是只有可以轉成 JSON 格式的物件才可以使用,例如 function 就沒有辦法被轉成 JSON。
| 1 | var obj1 = { fn: function(){ console.log('aaa') } }; | 
驗證
| 1 | console.log(obj1.fn); // ƒ (){ console.log('aaa') } | 
被複製物件內的 function 會消失,所以這個方法只能用在單純只有資料的物件。
jQuery
我們還可以使用 jQuery 的 $.extend 來做到深拷貝
| 1 | var obj1 = { name: 'Dylan', family: { | 
驗證
| 1 | console.log(obj1 === obj2); // false | 
lodash
這是一個非常多人在使用的函式庫,我們可以使用他所提供的 _.cloneDeep 來做到深拷貝,效能佳,且使用簡單易讀。
| 1 | var obj1 = { name: 'Dylan', family: { | 
驗證
| 1 | console.log(obj1 === obj2); // false | 
參考資料
[Javascript] 關於 JS 中的淺拷貝和深拷貝
關於JAVASCRIPT中的SHALLOW COPY(淺拷貝)及DEEP COPY(深拷貝)