先備知識:
JS 資料型態 ( Data Types )x7
- boolean 布林 (true / false)
- null 空
- undefined 未定義
- number 數值
- string 字串
- symbol
- Object 物件
- 基本資料型態(Primitive data types):1 ~ 6
複合資料型態(Composite data types) :7 - Object
- 基本上不屬於基本型態的,都是 Object
- Array 是 Object 子型別(subtype):以
typeof
查詢會得到"object"
array 與 object
// ary [1, 2, 3] // 一維陣列 [[1, 1]], [2, 2], [3, 3]] // 二維陣列
// obj { // ---單層物件 "1": "Amy", "2": "Betty", "3": "Claire" } { // ---多層物件 "1": { "name": "Amy", "age": 10 }, "2": { "name": "Betty", "age": 12 }, "3": { "name": "Claire", "age": 8 } }
"一般狀況下"的賦值動作:
pass by value ( 基本型態 )
若變數 a、b 為 基本資料型態時 —傳值(pass by value)
var a = 1 var b = a
a 、 b 記憶體位址獨立, a、b 內容各自修改時並不會互相影響對方的值(b 有開新的記憶體位址並且把 a 的值 copy 過來)。
pass by reference ( 複合型態 )
若變數 a、b 為 複合資料型態時( Object / Array / Function) —傳址(pass by reference)
var a = { count: 1} var b = a
a 、 b 記憶體位址共用( b 沒有建立新的位址,而是引用 a 的記憶體位址) ,此時若賦予 b 新的值 也會影響到 a(循著引用的記憶體位址去改 a )
pass by sharing
在 function 改複合資料型態值的兩種狀況:
var a = { count: 1}
var b = a
狀況 1:直接修改屬性值 =by reference
function changValue(val){ val.count = 2 } changValue(a) // b也同時被改為 {count : 2}
狀況 2 : 重新賦值整個 object (object literal) =by value
function changValue(val){ val = { count: 2} } changValue(a) // b 依然是 {count : 1}
淺拷貝 & 深拷貝
淺拷貝(Shallow Copy)
- 複製到同一個參考位址(共用同一個記憶體位址)
深拷貝(Deep Copy)
- 複製整個值包含至深層,存進獨立的記憶體位置,不影響本來的參考
對 array / object 複製“值”的幾種常見方法
方法1. ary.slice(0)
或ary.concat()
(陣列)
僅限一維陣列 且 陣列值不含obj
let ary = [1, 2, 3] let newAry = ary.slice(0) //或let newAry = ary.concat() //---- ary === newAry // false newAry.push(4) // ary = [1, 2, 3] newAry = [1, 2, 3, 4]
方法2. Array.from(ary)
(陣列)
陣列:pass by value 多維陣列也OK!
let ary = [[1,1], [2,2]] let newAry = Array.from(ary) //---- newAry.push([3.3]) newAry[0]=[0,0] // ary = [[1,1], [2,2]] newAry = [[0,0], [2,2], [3,3]
方法3. [...ary] / {...obj}
ES6展開符 (陣列 / 物件)
展開符
...
展開陣列再裝進[]
空陣列 (ES6 寫法)let ary = [1, 2, 3] let newAry = [...ary] newAry.push(4) // ary = [1, 2, 3] newAry = [1, 2, 3, 4]
多維陣列時只有拷貝到陣列第一層
let ary = [[1,1], [2,2]] let newAry = [...ary]
console.log 查詢是否相等:
ary === newAry // false ary[0] === newAry[0] // true
⇧雖然 ary 與 newAry 已為不同的記憶體位址,但是深層卻仍是引用址。
故下列兩例,對 newAry 操作外層時不會改變到 ary:
newAry.push([3,3]) newAry[0]=[0,0] // ary = [[1,1], [2,2]] // newAry = [[0,0], [2,2], [3,3]]
操作內層時會一起影響到 ary:
newAry[0].push(9) newAry[1][1]=9 // ary = [[1,1,9], [2,9]] // newAry = [[1,1,9], [2,9]]
{... obj}
只能拷貝到物件第一層!let obj = { A: 1, B: { a: '2-1', b: '2-2' }, C: { a: '3-1', b: '3-2' } } let newObj = {...obj} //---- newObj.C.c = "3-3" //參照同個位址,obj也改了 newObj.A = 2 //第一層有獨立位址, 不影響 obj
方法4. Object.assign()
ES6 ( 陣列 / 物件)
Array:pass by value 多維陣列也OK!
let ary = [[1,1], [2,2]] let newAry = Object.assign([],ary) //---- newAry.push([3.3]) newAry[0]=[0,0] // ary = [[1,1], [2,2]] newAry = [[0,0], [2,2], [3,3]
Object:obj 只有第一層時 - pass by value
let obj = { a: 1, b: 2 } let newObj = Object.assign({},obj) //---- newObj.b = 3 // obj 維持 = { a: 1,b: 2} // newObj = { a: 1, b: 3 }
ps. 如果obj 裡面的value 本身已經是被淺拷貝過來的(記錄址而非值),newObj 裡存到的也是址
Object:第二層開始 pass by reference (相當於只能拷貝值至 object 第一層)
let obj = { A: 1, B: { a: '2-1', b: '2-2' }, C: { a: '3-1', b: '3-2' } } let newObj = Object.assign({},obj) //---- newObj.A = 2 //有獨立位址, 不影響 obj newObj.C.b = "3-22222" //沒有獨立位址, 影響到 obj
方法5. JSON.stringify()
/ JSON.parse()
( 陣列 / 物件)
- 陣列值/物件值內容不能包含 function 或 RegExp ...等
JSON.stringify()
先轉成字串;再JSON.parse()
再轉回原本的 物件/ 陣列陣列與物件都可多維/多層拷貝
let ary = [[1,1], [2,2]] let newAry = JSON.parse(JSON.stringify(ary)) //---- newAry.push([3.3]) // ary = [[1,1], [2,2]] newAry = [[1,1], [2,2], [3,3]
不同方法4 ,obj 可拷貝至深層
let obj = { A: 1, B: { a: '2-1', b: '2-2' }, C: { a: '3-1', b: '3-2' } } let newObj = JSON.parse(JSON.stringify(obj)) //---- newObj.C.b = "3-22222" //有獨立位址, 不影響 obj newObj.A = 2 //有獨立位址, 不影響 obj
不適用於值是 function 的或為 undefined 的(會遺失)。
其他
- 使用
lodash
的cloneDeep
... 等 library
結論 - 想拷貝值而非址時
Method | Ary 一維 | Ary 含多維 | obj 單層 | obj 含多層 | 限制 |
---|---|---|---|---|---|
ary.slice(0) ary.concat() | √ | 限值不含 object ... 等 | |||
Array.from(ary) | √ | √ | |||
[...ary] {...obj} | √ | √ | |||
Object.assign([],obj) Object.assign({},obj) | √ | √ | √ | ||
JSON.parse(JSON.stringify(ary)) JSON.parse(JSON.stringify(obj)) | √ | √ | √ | √ | 限值不含 function..等 |
參考資料
- Methods for deep cloning objects in JavaScript - LogRocket Blog
- How to Deep Copy Objects and Arrays in JavaScript
- Object.assign()|MDN
- 重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」? - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
- 談談 JavaScript 中 by reference 和 by value 的重要觀念
- JS 以陣列 Array 的複製談型別(下) - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
- Leon的程式心得
- 理解JS的浅拷贝与深拷贝
- JS-淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)
- Javascript 傳值傳址&深淺拷貝