理解 function 傳遞參數的機制

一個有點複雜的東西。

因為這個其實蠻複雜的,所以這裡希望你理解兩件事就好:

  1. 在 function 中修改引數的值會不會影響到外面的變數?
  2. JavaScript 中只有 pass by value 或 pass by sharing(嚴格來說是屬於 pass by value 的底下之一)

修改引數的值會不會影響到外面的變數?

先來看個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
function swap(a, b) {
var temp = a
a = b
b = temp
console.log(a, b)
}

var numberA = 10
var numberB = 20

console.log(numberA, numberB) // 10, 20
swap(numberA, numberB) // 20, 10
console.log(numberA, numberB) // 10, 20

這裡嘗試在 swap 中把兩個引數的值給交換,但顯然並不會影響到外面的變數(numberAnumberB

也就是說可以把 function 想成是這樣子處理引數的:

1
2
3
4
5
6
7
8
function swap(a, b) {
var a = numberA // 10
var b = numberB // 20
var temp = a
a = b
b = temp
console.log(a, b)
}

有點類似於把 numberA 的「值」拷貝給 anumberB 的「值」拷貝給 b

不知道你還記得在 從 Object 的等號來真正理解變數 提到的變數儲存方式嗎?此時 numberA 儲存的是「10」這個值,所以 a = numberA 只是把 10 這個數字給 a 而已,不管我們對 a 做什麼,它都不應該影響到 numberA

好,現在我們再看一個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function add(obj) {
obj.number++
obj.newValue = 'Boom!'
return 1
}

var myObj = {
number: 10
}

console.log(myObj.number) // 10
add(myObj)
console.log(myObj.number) // 11
console.log(myObj) // {number: 11, newValue: 'Boom!'}

按照前面的思維,這樣子的結果是合理的,因為當變數儲存的是物件時,這個變數儲存的值會是一個「記憶體位址」,所以此時的 function 是這樣子做處理:

1
2
3
4
5
6
function add(obj) {
var obj = myObj // 0x01
obj.number++
obj.newValue = 'Boom!'
return 1
}

因為 objmyObj 儲存的都是同一個記憶體位址,所以當 obj 去做修改或新增的時候,也很合理的會改到 myObj,畢竟「改的都是同一個記憶體位址裡的東西」。

忘記的話再複習一下這張圖:

pointer

再來是最後一個例子:

1
2
3
4
5
6
7
8
9
10
11
12
function add(obj) {
obj = {
number: 50
}
return 1
}
var myObj = {
number: 10
}
console.log(myObj) // {number: 10}
add(myObj)
console.log(myObj) // {number: 10}

用你原本的方式思考就好,在執行到 obj = {number: 50} 之前,其實都跟剛剛的情況是一樣的:

1
2
3
4
5
6
7
function add(obj) {
var obj = myObj // 0x01
obj = {
number: 50
} // 0x02
return 1
}

obj = {number: 50} 之後,obj 就已經被賦予一個新的記憶體位址,已經跟原本的 myObj 指向不同的地方,所以在之後不管你對 obj 做什麼,都不會影響到外面的 myObj

忘記的話再複習一下這張圖:

reassign

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 的差異可以參考這張圖:

ref

既然不是 pass by reference,那該叫什麼?就叫「pass by sharing」吧!可以把它想成是一種約定俗成。

不過如果你換個角度來思考的話,某種意義上 pass by sharing 也可以看做是一種 pass by value:

call by sharing(傳記憶體位置進去)其實就是 call by value 的一種,解釋的方式為:其實一樣是傳值的拷貝進去,只是這個值是記憶體位置。

但總而言之,我個人還是覺得分成 pass by value 跟 pass by sharing 的方式比較好理解。

總結

最後做個總結:

  1. 傳入 function 的變數是「非物件」型態,在 function 做任何修改都不會影響到原本的變數值。
  2. 傳入 function 的變數是 object ,在 function 裡做修改會影響到原本的變數值,但重新賦值不會。

另外想引用一下 Huli 的話:

搞清楚到底參數在操作的時候會有怎樣的行為。你要知道 JavaScript 傳 object 進去的時候,可以更改原本物件的值,但重新賦值並不會影響到外部的 object。只要知道這一點,其他的我覺得都沒那麼重要了。

其實這個問題好像一直都沒有最正確的解釋,但總而言之,我們其實想搞懂的是「函式怎麼操作引數」這件事,知道這樣就夠了。

參考資源

深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?

把 function 當作參數 重新認識迴圈
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×