JavaScript 基礎解析:理解 Pass By Reference
更新日期: 2024 年 9 月 14 日
嘿, 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 的開發者來說都是關鍵。
通過學習如何操作「原始類型」和「址類型」的數據,我們能更好地控制和預測代碼的行為。
最後,這些知識不僅有助於撰寫更有效和更可靠的代碼,還有助於在與其他開發者合作時,更好地理解和使用這些基本概念。