JavaScript 基礎解析:理解 Pass By Reference

更新日期: 2023 年 12 月 26 日

嘿, JavaScript 初學者!當你開始學習 JavaScript,有一個超重要但有點頭痛的概念等著你 —— 那就是「址傳遞」(Pass By Reference)。

可能一開始聽起來像火星文,但別擔心,我們會一步步弄清楚這是什麼意思,以及為什麼你需要知道它。

在這篇文章中,我們會用最簡單的方式講解什麼是「址傳遞」,為什麼它對寫 JavaScript 很重要。

我們會從基礎開始,用一些真實的例子來幫你理解。

無論你是剛開始學 JavaScript,還是已經會一些基本的東西,這篇文章都能幫你更好地搞懂這個概念。

準備好了嗎?讓我們開始吧!

數據類型概述

JavaScript 的數據類型,大致可以分為兩大類:「原始類型」和「物件類型」。

原始類型

這些是最基本的數據類型,包括數字(Number)、字符串(String)、布林值(Boolean)、未定義(Undefined)、空值(Null)、符號(Symbol),以及 ES2020 引入的 BigInt。

這些類型的特點是它們直接存儲「值」,並且在賦值或傳遞時是按「值」(Pass By Value)處理的。

物件類型

這些類型的數據,是通過「地址」存儲的,包括物件(Object)、數組(Array)和函數(Function)。

這意味著變數實際上,存儲對記憶體中的「數據地址」,(Pass By Reference)而不是數據本身。

當這些類型的數據,被賦值給另一個變數,或作為參數傳遞給函數時,實際上傳遞的是數據的「地址」。

Pass By Value vs Pass By Reference

Pass By Value(值傳遞

在值傳遞中,當數據從一個變數傳遞給另一個變數時,其「值」的副本會被創建和傳遞。

這意味著原始變數和新變數獨立存在,對一個變數的修改不會影響另一個。

JavaScript 中的原始數據類型(如數字、字符串)遵循值傳遞。

當我們談論「值傳遞」時,可以這樣理解:

假設你有一張漂亮的明信片,你給朋友寄了一張相同的複製品。

現在,你和你的朋友各自擁有一張明信片。

即使你的朋友在他的明信片上畫上了太陽,也不會影響到你手中的原始明信片。

這就像在 JavaScript 中,當你把一個變數(比如數字或字符串)賦值給另一個變數時,你實際上是在創建一個全新的副本,原始變數和新創建的變數是完全獨立的。

Pass By Reference址傳遞)

在「址傳遞」中,不是傳遞值的副本,而是傳遞對該數值在記憶體所儲存的位置(簡稱地址)。

換句話說,當多個變數指向同一個物件,對這個物件的任何修改,都會反映引用該物件的相關變數上。

JavaScript 中的物件類型(如物件、數組)遵循址傳遞。

想像一下,你和你的朋友共享一個線上播放列表(比如 Spotify 的播放列表)。

當你把這個播放列表的連結分享給朋友時,你們都可以訪問並修改這個播放列表。

如果你的朋友添加了一首新歌到這個播放列表,當你下次打開這個播放列表時,也能看到這首新歌。

在這裡,播放列表的連結就像是在 JavaScript 中物件的「地址」。

無論是你還是你的朋友對播放列表所做的任何更改,都會反映在同一個播放列表上。

因為你們共享的是對這個播放列表的超連結(地址的概念),而不是它的副本。

理解這兩種傳遞方式的區別對於撰寫高效、無錯誤的 JavaScript 代碼至關重要。

它們影響著變數間的交互方式,以及你如何操作和管理數據。

Pass By Reference 在 JavaScript 中的應用

當我們在一個函數內部,改變一個物件或數組的內容時,這些改變會反映在數組上,因為這些類型的數據是通過址傳遞的。

因此,函數內部對這些參數的任何修改,都會影響到原始數據。

這種行為對於設計和理解其輸入數據的函數至關重要。開發者需要明確了解哪些操作會對原始數據產生副作用。

改變物件屬性

我們以實際代碼示範說明:

const spaceship = {
  homePlanet : 'Earth',
  color : 'silver'
};
 
let paintIt = obj => {
  obj.color = 'glorious gold'
};
 
paintIt(spaceship);
 
spaceship.color // 返回 'glorious gold'

我們的函數 paintIt() 永久地改變了我們的宇宙飛船物件的顏色。

物件重新賦值

然而,對宇宙飛船變數的重新賦值不會以相同的方式工作:

let spaceship = {
  homePlanet : 'Earth',
  color : 'red'
};

let tryReassignment = obj => {
  obj = {
    identified : false, 
    'transport type' : 'flying'
  }
  console.log(obj) // 顯示 {'identified': false, 'transport type': 'flying'}

};

console.log(spaceship) // 仍然返回 {homePlanet : 'Earth', color : 'red'};

當我們將 spaceship 物件作為參數傳入 tryReassignment 函數時,傳遞的不是物件本身,而是對物件的地址

但當我們在函數內部對 obj 參數進行的重新賦值,只會改變函數內部的「地址」。

意味著該新地址已經與原先的第址無關,進而不會影響到外部的 spaceship 變量。

逐步解釋

一、傳入物件 A 的地址(假如地址為 1234):

當物件 A 作為參數傳入 tryReassignment 函數時,函數內部的參數(例如 obj)會接收這個地址(1234),這意味著 obj 現在指向物件 A。

二、在函數內部更改參數地址:

當你在函數內部將 obj 重新賦值為一個新物件時,你其實是讓 obj 參數轉變成一個全新的內存位置(假設地址是 5678)。這個操作改變的是 obj 參數的地址,而不是外部物件 A 的地址。

三、結果:

因此,函數內部的 obj 參數指標變成了 5678,它現在指向新的物件 B。

外部的物件A(及其指標 1234)不受影響,仍然指向原來的物件。

兩者比較

修改物件的屬性

在 paintIt 函數中,我們沒有試圖改變 obj 參數本身的指向(即它指向哪個物件的地址),而是改變了它所指向物件的內部屬性。

這裡,obj.color = ‘glorious gold’ 這行代碼,實際上是在修改 obj 所指向的那個物件(在這個例子中是 spaceship 物件)的 color 屬性。

因為 obj 參數是對 spaceship 物件的參考,所以當我們修改 obj 的屬性時,實際上是在直接修改 spaceship 物件本身的屬性。

這就是為什麼 paintIt 函數能夠永久改變 spaceship 物件的顏色。

重新賦值與改變屬性的差別

重新賦值意味著,將變數原先的地址更改成另一個全新的地址,這就像在 tryReassignment 函數中所嘗試的那樣。

當你在函數內部對參數進行重新賦值時,你是在改變該參數所指向物件的記憶體位置,但這並不影響原始變量的地址(在我們的例子中是 spaceship)。

總結

在這篇文章中,我們探討了 JavaScript 中的「按值傳遞」和「按址傳遞」,並透過實際代碼示例展示了它們在日常編碼中的應用。

理解這兩種數據傳遞方式,對於任何希望精通 JavaScript 的開發者來說都是關鍵。

通過學習如何操作「原始類型」和「址類型」的數據,我們能更好地控制和預測代碼的行為。

最後,這些知識不僅有助於撰寫更有效和更可靠的代碼,還有助於在與其他開發者合作時,更好地理解和使用這些基本概念。

Similar Posts