[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
時
在這時記憶體會建立儲存空間,與對應的編號給予 1
與 a
, 且 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 |
紅色
的表示為 整體物件 的主體
你會發現 a
與 b
參考的位置都為 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 的參考不變,仍然是 0X003
但 0X003
所關聯的 0X002
卻在 a.id = 2
時, 被改寫了
而因 a 與 b 參考的都是 0X003
, 所以 b 回傳的結果就也很自然的產生變化
結論
其實最好的區別 By Value & By Reference 的方式,最快的方式就是直接區分參數的類型是不是 原始型別 (primitive type)
就可以了
而本文的重點,其實就在於建立
JavaScript 在定義或宣告變數時,並
不是直接附值
而是建立參考
這個概念,藉由這個觀點,你可以試著思考一下文頭的範例
相信會更進一步的加深這觀念的印象
注意上面的圖表只是方便理解
建立參考
的概念,不代表記憶體底下的實際運作