iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 14
0
Modern Web

一起挑戰 JavaScript 30 吧!系列 第 14

Day 14 - JavaScript References VS Copying

  • 分享至 

  • xImage
  •  

今天不做作品,而是討論一下在 JavaScript 中的複製。或許你有遇過下面問題

let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2[2] = 5;
arr2;  // [1, 2, 5]
arr1;  // [1, 2, 5]

原本的 arr1 居然也被更改了!假如你也正有著個疑惑那就接著看下去吧!如果你已經知道原因那也看下去吧!

基本型別(Number, String, Boolean, undefined, null)

先看範例

let a = 1;
let b = a;
b = 3;
b;  // 3
a;  // 1

a 是基本型別並 b = a時,a 實際上複製一份自己的值給 b 這個變數,也因此當更改 b 的值的時候 a 的值不會一起被更改,而這種特性適用所有基本型別。

Array

假設現在有一 array

let fruits = ['watermelon', 'papaya', 'banana', 'lemon'];

接著用新的變數複製一份 fruits 的值並更改內容

let fruits2 = fruits;
fruits[0] = 'pamelo';
fruits2;  // ['pamelo', 'papaya', 'banana', 'lemon']
fruits;  // ['pamelo', 'papaya', 'banana', 'lemon']

原本的 fruits 竟然也被改到了!
這是因為當對 array 使用 fruits2 = fruits 時,不同於基本型別複製了「值」,實際上是複製了一份指向 ['watermelon', 'papaya', 'banana', 'lemon'] 的參考點(reference),或者可以說現在 fruits 以及 fruits2 都指向相同的 array ['watermelon', 'papaya', 'banana', 'lemon'],所以當更改其中一個時,另一個也會被改變。

既然 fruits2 = fruits 不會複製實際值,那我們要如何複製 array 的值呢?

請參考使用下列幾種方法,如果有其他方法也歡迎補充~

Array.from(..)

這個方法之前在將 array-like 轉成 array 時常用到,但它也可以用來複製 array 喔!

let fruits3 = Array.from(fruits);
fruits3[0] = 'pamelo';
fruits3;  // ['pamelo', 'papaya', 'banana', 'lemon']
fruits;  // ['watermelon', 'papaya', 'banana', 'lemon']

原本的 fruits 沒有被改到,很好!

slice(..)

slice() 也可以達成一樣的效果,它原本的使用目的是取出部分 array 內容,但同樣能用於複製

let fruits4 = fruits.slice();  // 若沒有帶入參數則會是整個 array
fruits4[0] = 'pamelo';
fruits4;  // ['pamelo', 'papaya', 'banana', 'lemon']
fruits;  // ['watermelon', 'papaya', 'banana', 'lemon']

... (Spread Operator)

第三種方法是使用 ES6 的新語法展開運算子(Spread Operator)

let fruits5 = [...fruits];  // 若沒有帶入參數則會是整個 array
fruits5[0] = 'pamelo';
fruits5;  // ['pamelo', 'papaya', 'banana', 'lemon']
fruits;  // ['watermelon', 'papaya', 'banana', 'lemon']

Object

上面提到的特性(複製參考點而不是實際的值)不只適用 array,同樣也適用於 object。
舉例來說

let person = {
    name: 'Henry',
    age: 66
}

let man1 = person;
man1.age = 30;
man1;  // {name: 'Henry', age: 30}
person;  // {name: 'Henry', age: 30}

man1 同樣只複製到 {name: 'Henry', age: 66} 的參考點而不是實際值,以下列出 object 的複製方法

Object.assign(..)

let man2 = Object.assign({}, person, {age: 87, job: 'teacher'});
man2;  // {name: 'Henry', age: 87, job: 'teacher'}
person;  // {name: 'Henry', age: 66}

原本的 object 沒有被動到,耶!

目前我只有想到這個複製方法,如有其他方法歡迎提供

上述的方法雖然可以複製,但其實只是淺層複製(只複製一層),也就是當 object/array 中的項目有 object/array 時會失效,見下方範例

let cat = {
    name: 'Miao',
    birthYear: 2006,
    description: {
        friendly: false,
        fat: true
    }
}

let cat2 = Array.from(cat);
cat2.birthYear = 2012;
cat2.description.fat = false;

cat2;  // {name: 'Miao', birthYear: 2012, description: {friendly: false, fat: false}}
cat;  // {name: 'Miao', birthYear: 2006, description: {friendly: false, fat: false}}

可以看到 cat.description.fat 還是被改了

如果想要完整複製這種 object/array,可以考慮寫一個 function 並使用迴圈來複製每一層

array 和 object 除了上述的幾種方法其實還有一種方法,有用但很糙,請將它視為沒有辦法的辦法

let cat3 = JSON.parse(JSON.stringify(cat));

如果試著更改 description 中的內容,會發現原本的 cat 沒有被改到,這是因為 JSON.stringify 先將 object 轉成了 string(此時原本的參考點就不復存在了)並透過 JSON.parse 還原

希望這篇提到的內容對於你了解複製值&參考點有幫助,沒有的話也沒關係。

Reference


上一篇
JS30 Day 13 - Slide in on Scroll
下一篇
JS30 Day 15 - LocalStorage
系列文
一起挑戰 JavaScript 30 吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言