JavaScript 核心觀念(29)-物件-Call by Reference 還是 Call by Sharing

前言

接下來談談什麼是 Call by Reference 還是 Call by Sharing

Evaluation strategy (求值策略)

Evaluation strategy 簡單來講是程式語言的運作方式,在維基百科中可以看到以下名詞

  • 嚴格求值(Strict evaluation)
    • 應用次序(Applicative order)
    • 傳值呼叫(Call by value)
    • 傳參照呼叫(Call by reference)
    • 傳共享物件呼叫(Call by sharing)
    • 傳復件-恢復呼叫(Call by copy-restore)
    • 部分求值(Partial evaluation)
  • 非嚴格求值(Non-strict evaluation)
    • 正常次序(Normal order)
    • 傳名呼叫(Call by name)
    • 傳需求呼叫(Call by need)
    • 傳巨集展開呼叫(Call by macro expansion)
  • 非確定性策略(Nondeterministic strategies)
    • 完全β歸約(Full β-reduction)
    • 傳預期呼叫(Call by future)
    • 最佳求值(Optimistic evaluation)

但是在這邊主要是認識 傳值呼叫(Call by value)、傳參照呼叫(Call by reference)以及傳共享物件呼叫(Call by sharing)這三個

首先先講講 Call by value,Call by value 又稱之為 pass by value,在這邊我們先看一下下面的範例程式碼

1
2
3
4
5
6
7
8
9
10
function fn (a, b) {
var temp = 100;
a = temp;
b = a;
}

var x = 10;
var y = 20;

fn(x, y);

上面的程式碼這邊簡單說明一下,我建立了兩個全域變數分別是 xy 以及一個函式叫做 fn 並且可以傳入兩個參數,接下來我呼叫了 fn 並傳入 xy

接下來在 fn 中我們做了一些行為,首先建立了一個區域變數 temp 並且賦予值是 100

然後我們將傳入的參數 ab 分別做了修改,當我們修改完畢之後,請問 ab 的結果會如何?

1
2
3
4
5
6
7
8
9
10
11
12
function fn (a, b) {
var temp = 100;
a = temp;
b = a;
}

var x = 10;
var y = 20;

fn(x, y);

console.log(x, y); // 10, 20

在這邊我們函式的參數概念有點類似宣告一個新的變數,所以 ab 分別都會是不同的記憶體位置,上面這個概念就是 Call by value 概念,簡單來講就是傳入假的變數,實際上是建立另一個記憶體空間存放,更白話一點的說法就是,你不會因為修改另一個值而影響到原始的值。

接下來講講 Call by reference,Call by reference 與 Call by value 是相反的概念,在這邊傳入的值,如果修改是會影響到原有的變數,例如以下程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fn (a) {
var temp = 100;
a.x = temp;
a.y = a.x;
}

var obj = {
x: 10,
y: 20,
}

fn(obj);

console.log(obj.x, obj.y); // 100, 100

看起來是非常符合 Call by reference 概念,那如果我們基於這個概念將 a 賦予一個新物件會發生何事呢?外層的 obj 也會跟著變嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fn (a) {
a = {
b: 100,
c: 50
}
}

var obj = {
x: 10,
y: 20,
}

fn(obj);

console.log(obj); // { x: 10, y: 20, }

在這邊預期結果應該是要變成 { b: 100, c: 50 } 畢竟前面有講到 Call by reference 的概念簡單來講就是整個原始路徑參考過去,但這邊卻還是保持 { x: 10, y: 20, },這邊原因是為什麼呢?其實 JavaScript 在維基百科中是有說明它是屬於 Call by sharing,在這邊直接擷取維基百科的說法

如果要達成傳參照呼叫(Call by reference)的效果就需要傳一個共享物件,一旦被呼叫者修改了物件,呼叫者就可以看到變化(因為物件是共享的,沒有拷貝)。

這一段的意思如果我們套用到上面的程式碼來解釋的話,大概就是我們可以傳入一個共享物件到 fn 函式中並修改,並且我們也可以修改傳入的這個共享物件,將他修改成另一個物件,因此以後只要看到新的 { } 就代表這是一個新的 reference 唷。

參考文獻