J 筆記 - Deep Clone Array

J 筆記 - Deep Clone Array

之前有介紹過如何 Clone an Array,但可以知道的問題是,如果今天是 Array 裡面又有更深層的 Array 的話(Deep Array),再複製陣列上就會有點問題,也如同上篇的 Only one level 的結論。因此,這篇就來介紹兩種 Deep Clone Array 的方法吧!

1
2
3
4
5
6
7
8
9
10
11
const nestedArray = ['ES5', ['ES6', ['ES7']], ['ES8']];

// Using JavaScript
JSON.parse(JSON.stringify(nestedArray));

// Using Lodash
_.cloneDeep(nestedArray);

// Using Recursion
const clone = (items) => items.map(item => Array.isArray(item) ? clone(item) : item);
clone(nestedArray);

Copying a Value type

先來個最基本的數組,這部分應該很好理解,宣告一個 value。而且如果我們更改 valueCopy,不會影響原本的 value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let value = 9527;
let cloneValue = value;
// clone value

console.log(cloneValue);
// 9527

cloneValue = 100;
// Change cloneValue
console.log(cloneValue);
// 100

console.log(value);
// 3

Copying a Reference type

如果上篇所述,為什麼原始陣列也會受到影響?那是因為複製的不是數組本身,而是指向數組的記憶體位址。

1
2
3
4
5
6
7
8
9
10
11
const initarray = ['ES5', 'ES6', 'ES7'];

const initarray2 = initarray;

initarray2.push('❌');

console.log(initarray2);
// ["ES5", "ES6", "ES7", "❌"]

console.log(initarray);
// ["ES5", "ES6", "ES7", "❌"]

因此,有了上篇的解決方案:

1
2
3
4
5
6
7
8
9
10
11
const initarray = ['ES5', 'ES6', 'ES7'];

const initarray2 = [...initarray];

initarray2.push('✔️');

console.log(initarray2);
// ["ES5", "ES6", "ES7", "✔️"];

console.log(initarray);
// ["ES5", "ES6", "ES7"];

Deep Clone

知道了上面的方法後,讓我們現在來看看透過 Spread ...Array.from 雖然可以複製陣列,但只能複製 Only one level 的問題,而無法解決巢狀亦又或者是 Deep Clone

可以發現,原本的 nestedArray 的陣列一並沒有改變 ES5,這很符合我們上邊敘述複製 Only one Level 的方法,不過後面的 ES6 就被改為 Deep!!! 了!

1
2
3
4
5
6
7
8
9
10
const nestedArray = ['ES5', ['ES6', ['ES7']], ['ES8']];
const copynestedArray = [...nestedArray];

// Make some changes
copynestedArray[0] = 'ES0';
copynestedArray[1][0] = 'Deep!!!';
console.log(copynestedArray); // ['ES0', ['Deep!!!', ['ES7']], ['ES8']]

console.log(nestedArray);
// ['ES5', ['Deep!!!', ['ES7']], ['ES8']]

JSON & Recursion & lodash

接下來,我們來看看透過 JSON 的實作結果,如果有興趣看 lodash 的方法也可以去官網查看,這是一套滿多人在使用,可以更簡潔寫 code 的 js 套件。

https://lodash.com/docs/

1
2
3
4
5
6
7
8
9
10
const nestedArray = ['ES5', ['ES6', ['ES7']], ['ES8']];
let JSONcopynestedArray = JSON.parse(JSON.stringify(nestedArray));

// Make some changes
JSONcopynestedArray[0] = 'ES0';
JSONcopynestedArray[1][0] = 'Deep!!!';
console.log(JSONcopynestedArray); // ['ES0', ['Deep!!!', ['ES7']], ['ES8']]

// ✔️ Nested array NOT affected
console.log(nestedArray); // ['ES5', ['ES6', ['ES7']], ['ES8']]

另外,也可以使用 Recursion 遞迴 來完成複製

1
2
3
4
5
6
7
8
9
10
11
12
const clone = (items) => items.map(item => Array.isArray(item) ? clone(item) : item);

const nestedArray = ['ES5', ['ES6', ['ES7']], ['ES8']];
var RecursionArray = clone(nestedArray)

// Make some changes
RecursionArray[0] = 'ES0';
RecursionArray[1][0] = 'Deep!!!';
console.log(RecursionArray); // [ 'ES0', [ 'Deep!!!', ['ES7'] ], ['ES8'] ]

// ✔️ Nested array NOT affected
console.log(nestedArray); // ['ES5', ['ES6', ['ES7']], ['ES8']];

Values Not Compatible with JSON

Note!!! 值得注意的是,如果數值與 JSON 不兼容,轉譯的時候會出現錯誤

1
2
3
4
5
6
function copynestedArray(array) {
return JSON.parse(JSON.stringify(array));
}

copynestedArray([1, undefined, 2]) // [1, null, 2]
copynestedArray([document.body, document.querySelector('p')]) // [{}, {}]

參考資料