[JavaScript] JavaScript 重要觀念: By Value & By Reference

通常 初學者 在撰寫 JavaScript 腳本時,相信都有碰到過一個問題,就是有時候會 沒有辦法去掌握當前的 變數 (Variable) 現在的狀態是什麼 ?,尤其是針對 物件 (Object),的資料進行操作的時候,會發現物件內的元素不如自己的預期

下面是常見的實際案例:

可以閱讀後,去執行看看,如果執行的結果不是你預期的,那就應該認真把這篇文章看完了

let arr = []
let data = {
  id: 0
}

for(let i = 0; i < 3; i++){
  arr[i] = data
  data.id = i
}

console.log(arr)


By Value & By Reference

為什麼執行後, arr 的資料不是預期的

(3) [{…}, {…}, {…}]
0:{id: 0}
1:{id: 1}
2:{id: 2}

而是

(3) [{…}, {…}, {…}]
0:{id: 2}
1:{id: 2}
2:{id: 2}

答案的關鍵字就是 By Value & By Reference 的觀念

我們來依序介紹


By Value

By Value 的概念非常簡單,以下面的代碼為例:

let a = 1
let b = 1

上方宣告了兩個變數 a 、 b ,對這兩個變數各別賦予一個 數字 (Number) 為這兩個變數當作參考值

這個就叫 By Value , 對! 就只是這樣

只要給予變數的參考值是 原始型別 (primitive type) , 我們就稱為 By Value 的附值方式

在 JavaScript 中 primitive type(Boolean, String, Number, null, undefined) 都屬於 By Value。

我們來看一個稍微有些複雜的例子

let a = 1
let b = a
a = 2
console.log(b) // 請問現在的 b 會是什麼呢?

我們都知道 JavaScript 程式碼是由上往下執行的,我們從第一行開始由上往下閱讀

答案出來的結果會是 1 這還滿直觀的

但在下列我們用一個簡單的圖表來顯示出在記憶體位置中,發生的事:


當我們去宣告 let a = 1

在這時記憶體會建立儲存空間,與對應的編號給予 1a , 且 a 會去參考 1 的記憶體位置

記憶體編號 儲存值
0X001 1
0X002 0X001 a

所以當我們今天去回傳 a 的值,回傳的結果便會是 1


再來我們宣告 let b = a ,此時的記憶體空間如下:

記憶體編號 儲存值
0X001 1
0X002 0X001 a
0X003 0X001 b

此時, b 會去取得 a 目前的參考位置,也就是 0X001

當我們今天去回傳 b 的值,回傳的結果也會是 1


再來我們執行 a = 2

這時的圖表如下:

記憶體編號 儲存值
0X001 1
0X002 0X004 a
0X003 0X001 b
0X004 2

在此時,我們去更改 a 的 參數值時,藉由上面圖表的示意,可以知道 a 的更動是不會變更到 b

你可以發現 a 與 b 所參考的位置,是 完全不相干且不被影響 ,只要給予變數的參考值是 原始型別 (primitive type) 時,都可以使用上方的方式進行理解

不過當參數值不是 原始型別 (primitive type) 而是一個 物件(Object) 或是 陣列(Array) 時,那就有點複雜了,也就是我們待會要介紹的 By Reference


By Reference

一樣我們給予一個範例的編碼,來用於理解:

let a = {
  id: 0
}

let b = a
a.id = 2

console.log(b.id) // 會回傳的結果會是 0 還是 2 呢 ?

你可以試著回傳一下 b 的結果,結果如下:

{
 id: 2
}

神奇吧,其實剛剛我們在介紹 By Value 的時候,應該有發現一件事情:

JavaScript 在定義或宣告變數時,並 不是直接附值 而是 建立參考

我們試著用剛剛的圖表來進行模擬:

let a = {
  id: 0
}

let b = a

圖表如下:

記憶體編號 儲存值
0X001 0
0X002 0X001 prop id
0X003 {0X002}
0X004 0X003 a
0X005 0X003 b

紅色的表示為 整體物件 的主體

你會發現 ab 參考的位置都為 0X003


好的,相信看到這裡可能已經知道接下來會發生什麼事了

再來當我們去執行 a.id = 2 的編碼時:

記憶體編號 儲存值
0X001 0
0X002 0X001 prop id
0X003 {0X002}
0X004 0X003 a
0X005 0X003 b

實際上我們修改到的就是 0X002 這個位置的資料,如下:

記憶體編號 儲存值
0X001 0
0X002 0X006 prop id
0X003 {0X002}
0X004 0X003 a
0X005 0X003 b
0X006 2

但實際上 a 與 b 的參考不變,仍然是 0X0030X003 所關聯的 0X002 卻在 a.id = 2 時, 被改寫了

而因 a 與 b 參考的都是 0X003 , 所以 b 回傳的結果就也很自然的產生變化


結論

其實最好的區別 By Value & By Reference 的方式,最快的方式就是直接區分參數的類型是不是 原始型別 (primitive type) 就可以了

而本文的重點,其實就在於建立

JavaScript 在定義或宣告變數時,並 不是直接附值 而是 建立參考

這個概念,藉由這個觀點,你可以試著思考一下文頭的範例

相信會更進一步的加深這觀念的印象

注意上面的圖表只是方便理解 建立參考 的概念,不代表記憶體底下的實際運作

Facebook 功能: