iT邦幫忙

2023 iThome 鐵人賽

DAY 20
1
Modern Web

GPT 救救我!菜雞小海獺的前端成長之旅系列 第 20

D20 - JS - 基礎 - 物件 - 2:傳值、傳參考與深淺拷貝

  • 分享至 

  • xImage
  •  

傳值(call by value)

https://ithelp.ithome.com.tw/upload/images/20230921/20161801BbyrQwMVxi.png
Fig. 1. call by value 示意, PJCHENder,https://pjchender.blogspot.com/2016/03/javascriptby-referenceby-value.html

在 JS 中,建立屬於原始型別(Primitive type)的變數 a ( let a = 1 ) 與 b ( let b ) 時,這兩個變數會存於個別獨立的記憶體位址(0x001、0x002)。將 a 賦值給 b 時 ( b = a ),會將 0x001 記憶體位址指向的空間的資料,複製到 0x002 記憶體位址指向的空間。

// number
let num = 1;
let newNum;
newNum = num;
num = 3;

console.log(num === newNum); // false
console.log(newNum); // 1

備註:原始型別(Primitive type)為 string、number、bigint(任意大正整數)、boolean、undefined、symbol、null 都屬於 call by value。


傳參考(call by reference)

https://ithelp.ithome.com.tw/upload/images/20230921/20161801tAZfi1ICH0.png
Fig. 2. call by reference 示意, PJCHENder,https://pjchender.blogspot.com/2016/03/javascriptby-referenceby-value.html

類似的狀況,變數 a 的型別為 object 時,建立變數 b 並將 a 賦值給 b (b = a),實際上是使 a 與 b 的記憶體指向同一個位址(0x001)。此時再更改 a 或 b 的值,a 或 b 都會被一同變更。

你以為賦的是值,但其實只是複製了一份參考用的地址。

// object 1
let user = {
  name: "amy",
};

let newUser;
newUser = user;
user.name = "otter";

console.log(newUser === user); // true;比較的是 reference
console.log(newUser.name); // otter

// object 2
let a = { animal: "otter" };
let b = { animal: "otter" };

console.log(a === b); // false;比較的是 reference

// 轉成字串再比較內容
let aStr = JSON.stringify(a);
let bStr = JSON.stringify(b);
console.log(aStr === bStr); // true

例外情況

  • 使用物件實字指定物件值時為 call by value
// object
let user = {
  name: "amy",
  habbit: "eat"
};

let newUser;
newUser = user;
user = { animal: "otter" }; // 物件實字

console.log(newUser); // name: 'amy', habbit: 'eat'
console.log(user); // animal: 'otter'
  • Pass by sharing:在函式作用域內的物件被重新賦值時,不會影響到外部物件
let user = { name: "amy" };

function reName(obj) {
  obj = { eat: "apple" };
  console.log(obj);
}

reName(user); // eat: 'apple'
console.log(user); // name: 'amy'

解決辦法

淺拷貝(shallow copy)

Object.assign()

// 語法
Object.assign(target, ...sources)
// 等同於
Object.assign(target, {obj1},{obj2},{obj3},...)

參數說明:

  • target:目標物件
  • ...sources:來源物件,等於 [{ obj1 }, { obj2 }, { obj3 }, ...]
  • 回傳值:將目標物件與(多個)來源物件合併得到最終物件。

所以剛剛的 object 1 例子可以這樣改寫,就不會誤更動物件了。

// 修改 object 1
let user = {
  name: "amy"
};

let newUser;
newUser = Object.assign({}, user); // 包成物件
user.name = "otter";

console.log(newUser === user); // false
console.log(newUser.name); // amy
console.log(user.name); // otter

展開運算子(spread syntax) ...

  • 展開運算子將可迭代的物件如字串或陣列展開。
  • 對物件實字來說,展開運算子會將物件以 key-value 的方式展開,並加入到新創的物件內。
// 修改 object 1
let user = {
  name: "amy"
};

let newUser;
newUser = { ...user }; // 使用展開運算子
user.name = "otter";

console.log(newUser === user); // false
console.log(newUser.name); // amy
console.log(user.name); // otter

深拷貝(deep copy)

JSON.parse(JSON.stringify(obj))

  • 先將物件以 JSON.stringify() 轉成字串,再以 JSON.parse() 轉成物件。這樣可以確保轉出來的是一個全新物件,且使用的是不同 reference。
// 修改 object 1
let user = {
  name: "amy"
};

let newUser;
newUser = JSON.parse(JSON.stringify(user)); // 物件轉字串再轉物件
user.name = "otter";

console.log(newUser === user); // false
console.log(newUser.name); // amy
console.log(user.name); // otter

……差點沒暈死過去 (つд⊂)
https://ithelp.ithome.com.tw/upload/images/20230921/20161801fxuSyZbhuh.jpg
Fig. 3. 遮臉海獺。原出處連結已死,這邊用的是宅宅新聞的獺圖❤️:https://news.gamme.com.tw/589666


參考資料

  • [筆記] 談談 JavaScript 中 by reference 和 by value 的重要觀念 ~ PJCHENder 那些沒告訴你的小細節,https://pjchender.blogspot.com/2016/03/javascriptby-referenceby-value.html
  • 重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」? - iT 邦幫忙,https://ithelp.ithome.com.tw/articles/10191057
  • JS基本觀念:call by value 還是reference 又或是 sharing? | by Charles Huang | Medium,https://medium.com/@mengchiang000/js%E5%9F%BA%E6%9C%AC%E8%A7%80%E5%BF%B5-call-by-value-%E9%82%84%E6%98%AFreference-%E5%8F%88%E6%88%96%E6%98%AF-sharing-22a87ca478fc
  • Object.assign() - JavaScript | MDN,https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
  • 展开语法 - JavaScript | MDN,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax
  • 關於JS中的淺拷貝(shallow copy)以及深拷貝(deep copy) | by Andy Chen | Andy的技術分享blog | Medium,https://medium.com/andy-blog/%E9%97%9C%E6%96%BCjs%E4%B8%AD%E7%9A%84%E6%B7%BA%E6%8B%B7%E8%B2%9D-shallow-copy-%E4%BB%A5%E5%8F%8A%E6%B7%B1%E6%8B%B7%E8%B2%9D-deep-copy-5f5bbe96c122
  • 六角學院課程影片:[傳參考特性 | 六角學院]
  • JavaScript 核心觀念(29)-物件-Call by Reference 還是 Call by Sharing | 是 Ray 不是 Array,https://israynotarray.com/javascript/20200904/1772972600/

上一篇
D19 - JS - 基礎 - 物件 - 1 - 宣告、取值與修改
下一篇
D21 - JS - 基礎 - 流程控制 - 條件判斷與例外處理
系列文
GPT 救救我!菜雞小海獺的前端成長之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
sponge1220
iT邦新手 4 級 ‧ 2023-09-21 00:47:32

CatBoxy iT邦新手 4 級 ‧ 2023-09-22 10:45:16 檢舉

額額

我要留言

立即登入留言