一個有點複雜的東西。
因為這個其實蠻複雜的,所以這裡希望你理解兩件事就好:
- 在 function 中修改引數的值會不會影響到外面的變數?
- JavaScript 中只有 pass by value 或 pass by sharing(嚴格來說是屬於 pass by value 的底下之一)
修改引數的值會不會影響到外面的變數?
先來看個例子:
1 | function swap(a, b) { |
這裡嘗試在 swap
中把兩個引數的值給交換,但顯然並不會影響到外面的變數(numberA
,numberB
)
也就是說可以把 function 想成是這樣子處理引數的:
1 | function swap(a, b) { |
有點類似於把 numberA
的「值」拷貝給 a
,numberB
的「值」拷貝給 b
。
不知道你還記得在 從 Object 的等號來真正理解變數 提到的變數儲存方式嗎?此時 numberA
儲存的是「10」這個值,所以 a = numberA
只是把 10 這個數字給 a
而已,不管我們對 a
做什麼,它都不應該影響到 numberA
。
好,現在我們再看一個例子:
1 | function add(obj) { |
按照前面的思維,這樣子的結果是合理的,因為當變數儲存的是物件時,這個變數儲存的值會是一個「記憶體位址」,所以此時的 function 是這樣子做處理:
1 | function add(obj) { |
因為 obj
跟 myObj
儲存的都是同一個記憶體位址,所以當 obj
去做修改或新增的時候,也很合理的會改到 myObj
,畢竟「改的都是同一個記憶體位址裡的東西」。
忘記的話再複習一下這張圖:
再來是最後一個例子:
1 | function add(obj) { |
用你原本的方式思考就好,在執行到 obj = {number: 50}
之前,其實都跟剛剛的情況是一樣的:
1 | function add(obj) { |
在 obj = {number: 50}
之後,obj 就已經被賦予一個新的記憶體位址,已經跟原本的 myObj
指向不同的地方,所以在之後不管你對 obj
做什麼,都不會影響到外面的 myObj
。
忘記的話再複習一下這張圖:
pass by value ? pass by sharing
接下來要來解釋最開始提到的第二件事:JavaScript 中只有 pass by value 或 pass by sharing。
在變數儲存的是「非物件」的資料型態時(string, number, boolean),在 function 中修改引數的值不會影響到外面的值,這種行為我們稱為「pass by value
」。
在變數儲存的是「物件」的資料型態時(object, array),在 function 中修改引數的值不會影響到外面的值,這種行為我們稱為「pass by sharing
」。
至於為什麼稱為 pass by sharing 而不是 pass by reference?
根據我在網路上找的資料,pass by reference 的定義是重新賦值時也會影響到原本的變數。意思是說像剛剛 obj = {number: 50}
這個動作執行後 myObj
也會變成 {number: 50}
。
不懂 pass by reference 跟 pass by sharing 的差異可以參考這張圖:
既然不是 pass by reference,那該叫什麼?就叫「pass by sharing」吧!可以把它想成是一種約定俗成。
不過如果你換個角度來思考的話,某種意義上 pass by sharing 也可以看做是一種 pass by value:
call by sharing(傳記憶體位置進去)其實就是 call by value 的一種,解釋的方式為:其實一樣是傳值的拷貝進去,只是這個值是記憶體位置。
但總而言之,我個人還是覺得分成 pass by value 跟 pass by sharing 的方式比較好理解。
總結
最後做個總結:
- 傳入 function 的變數是「非物件」型態,在 function 做任何修改都不會影響到原本的變數值。
- 傳入 function 的變數是 object ,在 function 裡做修改會影響到原本的變數值,但重新賦值不會。
另外想引用一下 Huli 的話:
搞清楚到底參數在操作的時候會有怎樣的行為。你要知道 JavaScript 傳 object 進去的時候,可以更改原本物件的值,但重新賦值並不會影響到外部的 object。只要知道這一點,其他的我覺得都沒那麼重要了。
其實這個問題好像一直都沒有最正確的解釋,但總而言之,我們其實想搞懂的是「函式怎麼操作引數」這件事,知道這樣就夠了。