iT邦幫忙

2021 iThome 鐵人賽

DAY 14
1
自我挑戰組

JS30 學習日記系列 第 14

Day 14 - Object and Arrays - Reference VS Copy

  • 分享至 

  • xImage
  •  

前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks、No Compilers、No Libraries、No Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

瞭解 JavaScript 的 Passed by ValuePassed by Reference 以及 Object 的 shallow copydeep copy


解析程式碼

JS 部分

Passed by Value

在 JavaScript 裡,如果變數的資料型態屬於 Primitive(原生型別),則在傳遞變數時,採用的是 Passed by Value (傳遞值)。

常見的 Primitive:
  • String
  • Number
  • BigInt
  • Boolean
  • Symbol

在下面的兩個例子,我們分別將agename傳遞給age2name2,之後改變age2name2的數值。結果原本的agename均不會受到影響,因為在傳遞過程是 Passed by Value,將原本存於agename的值複製一份給age2name2

let age = 100;
let age2 = age;
console.log(age,age2);

age = 200;
console.log(age,age2);
let name = 'Mes';
let name2 = name;
console.log(name,name2);

name = 'wesley';
console.log(name,name2);

  • 簡易概念圖

Passed by Reference

在 JavaScript 裡,如果變數的資料型態屬於 Object(物件型別),則在傳遞變數時,採用的是 Passed by Reference (傳遞位址)。

常見的 Object:
  • Array
  • Object

下面的例子,我們將陣列 players 傳遞給常數 team,之後將team[3](Poppy)改成Lux,再把兩個陣列都印到 console,會發現到兩個陣列的第四個位置都被修改成新的值(Lux)。

這是因為物件在傳遞的過程中採取的是 Passed by Reference,也就是將物件在記憶體上的位置傳遞給另一個物件(兩者共享同一個記憶體位置),所以只要更改其中一方,另一方也會受到影響。

const players = ['Wes','Sarah','Ryan','Poppy'];
const team = players;
console.log(players,team);
    
team[3] = 'Lux';
console.log(players,team);

如果想修改陣列的值又不影響另一個陣列的話,我們可以使用slice()來複製陣列並回傳新陣列(擁有自己的記憶體空間)。

下面的例子,我們呼叫players.slice()將陣列players完整的複製一份到新陣列中並傳遞給team2,之後再修改team2的第四個位置,這次我們發現到原來的陣列players並沒有被修改到。

注意:slice()進行的是shallow copy(淺複製)!!!

const team2 = players.slice()
team2[3] = 'Lux';

console.log(players,team2)

其他修改陣列的值又不影響另一個陣列的方法有:

  • concat() : 將多個陣列進行串聯和slice()一樣會回傳串聯後的新陣列。
  • Spread : 將陣列中的元素展開並逐個放到新陣列中。
  • Array.from() : 建立一個新的 Array 實體。
const team3 = [].concat(players);

//use the new ES6 Spread
const team4 = [...players];

const team5 = Array.from(players);

另一個例子,我們宣告物件person並把它傳遞給captain,在captain上新增number: 90後將兩個物件都印到 consle,此時會發現在captain新增的number: 90,也會被加到person上。會這樣是因為物件在傳遞過程是 Passed by Reference,兩物件同時指向一個記憶體空間。

const person = {
      name: 'Wes Bos',
      age: 80
};

const captain = person;
captain.number = 99;

console.log(person,captain);

如果不想修改到另一個物件的話,我們可以使用Object.assign(target, ...sources)來複製一個或多個物件的屬性到另一個目標物件,最後回傳目標物件。

下面我們利用Object.assign({},person,{number: 99,age:12});,將物件person{number:99,age:12}的屬性複製到目標物件({ })。

如果複製的多個物件的屬性有重複,以後面物件的屬性為準進行合併。舉例來說age是重複出現的屬性,最後複製屬性值時,要以後面出現的age:12為準合併物件。

注意 : Object.assign()做的是 shallow copy(淺複製),如果要複製的物件屬性包含子物件,就會複製到子物件的參照(reference)!!!。

const person = {
      name: 'Wes Bos',
      age: 80
};

const cap2 = Object.assign({},person,{number: 99,age:12});
console.log(person,cap2);

原來的person並沒有被改動到。

  • 簡單概念圖

Shallow Copy & Deep Copy

前面我們有提到,Object.assign()做的是 shallow copy(淺複製),若要複製的物件屬性包含子物件,就會複製到子物件的參照。

所以只要去改動新物件的子物件屬性值,就會連帶影響原本複製的物件子屬性值。

下面我們用Object.assign()複製來源物件(wes)的屬性到目標物件({}),再將目標物件傳遞給dev物件,要留意wes的屬性包含子物件屬性(social)。

例子中我們修改複製來的物件屬性(name)和子物件屬性(social.twitter),之後把wesdev物件印到 console 會發現複製的來源物件wes的屬性(name)不受影響,但wes的子物件屬性(social.twitter)卻被影響。

const wes = {
      name: 'Wes',
      age: 100,
      social: {
        twitter: '@wesbos',
        facebook: 'wesbos.developer'
      }
}

//shallow copy
const dev = Object.assign({},wes);
dev.name = 'Wesley';
console.log(wes,dev);

dev.social.twitter = '@coolman';
console.log(wes.social,dev.social);

如果不想改動複製來源物件的子屬性值(social.twitter),我們可以將要複製的物件用JSON.stringify()先轉換成 JSON String(因為是Primitive,所以是 Passed by Value),再用JSON.parse()把它還原成物件再回傳給dev2

下面我們一樣修改子物件的屬性值,但這次的原物件子屬性值並沒有受到影響。

const wes = {
      name: 'Wes',
      age: 100,
      social: {
        twitter: '@wesbos',
        facebook: 'wesbos.developer'
      }
}

// deep copy
const dev2 = JSON.parse(JSON.stringify(wes));//先轉成string再換回object
dev2.social.twitter = '@coolman';
console.log(wes.social,dev2.social);

補充資料:

JavaScript: var, let, const 差異
Array.prototype.concat()
Spread syntax (...)
Array.from()
Object.assign()
JSON.stringify()
JSON.parse()


上一篇
Day 13 - Slide In on Scroll
下一篇
Day 15 - LocalStorage and Event Delegation
系列文
JS30 學習日記31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言