2016年3月13日

[筆記] 談談 JavaScript 中 by reference 和 by value 的重要觀念

img
圖片來源:Udemy
在這堂課中,我們會說明在 JavaScript 中很重要的一個觀念,也就是說明什麼是 by value 和 by reference,正確了解這個觀念,能夠在寫程式的時候避免不必要的 bug 發生。
那我們先來看一段簡單的程式,接著再繼續說明 by value 和 by reference 的觀念。
我們先建立一個變數 a,並且值為 3 (var a = 3);接著,我們在建立變數 b,把 b 的值等於 a (b = a);最後,我們再把 a 的值改為 2 (a = 2)。
img
然後,我們把 a 和 b 呼叫出來,你猜猜看會得到什麼值:
img
這時候,我們會發現 a 的值變成了 2,而 b 的值仍然是 3,感覺好像蠻直觀的對吧。
可是,奇妙的地方讓我們繼續看下去:
我們一樣先建立一個變數 c,它是一個物件;接著建立變數 d,讓 d = c;最後我把物件 c 裡面的值做一下修改,從 Hello 改成 Hola;最後再把結果顯示出來:
img
你認為結果會是什麼呢?
img
看出有什麼特別之處了嗎?
在第一個例子中,我們將 a 變數的值做了修改之後,並不會影響到 b 的值;可是在第二個例子中,我們將 c 物件的值做了修改之後,連帶的影響到了 d 物件的結果!這就是我們在這篇文章中要說明的重要觀念!

By Value vs By Reference

By Value

讓我們先來說明一下什麼是 by value 。
當我們在建立 primitive type 的變數時(數字、字串、布林),假設我把a 指定成一個 primitive type 的時候,a 會在記憶體中存在一個自己的位置(假設叫做0x001)。這時候,當我指定另一個變數 b,它的值等同於 a 的時候,b 實際上會建立另一個獨立的記憶體位置(假設叫做0x002),接著再把 a 的值存在這個獨立的記憶體位置。也就是說, a 和 b 其實是存在於兩個不同的記憶體位置,因此彼此並不會乎相干擾影響,這種情況,我們就稱為 ++By Value++ ,而這種情形會發生在 primitive type 的變數。
img
在 JavaScript 中 primitive type(Boolean, String, Number, null, undefined)都屬於 By Value。

By Reference

在來看一下 By Reference。
當我將變數 a 設立成一個Object(或function)時,這時候,一樣會在記憶體中給它一個位置(假設叫做0x001);但是當我建立一個變數 b,並且把變數 b 的值等同於 a 時,這時候並不會再給它一個新的位置,而是一樣指定到物件 a 的位置(即0x001),實際上是不會有新的東西被建立,變數 a 和 b 都會被指稱到相同的位置(即0x001),因此,當 a 的值改變的時候 b 的值也會改變,因為它們實際上是指稱到相同的位置,這種情形我們就稱為 ++By Reference++,這樣情況會發生在 Object 和 Function 這種變數
img
經過這樣的說明,我想你就知道在上面的例子中,為什麼第一個例子改變(mutate)a 的值的時候,b 不會跟著改變;而改變 c 的值的時候,d 卻會跟著改變了吧。因為 a 和 b 是屬於 primitive type 的變數,而 c 和 d 則是 object。
一般來說,Primitive type 是 by value,而 Object 和 Function 則是 by reference,但有例外的情況,我們等等會看到。
這樣的規則即使是在 function 裡面的參數(parameter)也是一樣的,
讓我們再來看個例子,上面的部分是剛剛的例子二,我們在例子二的下面,建立一個 function,這個函式中名稱屬性的值為 changeGreeting ,裡面可以放參數 obj,程式內容屬性的值則是用來修改物件當中 greeting 屬性的值:
img
在這個例子中,我們可以看到,原本 c 和 d 物件中屬性 greeting 的值都是Hello,後來因為我修改了 c.greeting 的值為 Hola,所以 c 和 d 物件屬性 greeting 的值都變成了 Hola,最後我利用 function 的方式,改變 d 修改了 d.greeting 的值為 Hi,所以到了最後 c 和 d 物件屬性 greeting 的值都變成了 Hi。這就是因為 By reference 的緣
故!
img
在 JavaScript 中 Objects(Object, Array, Function)都屬於 By Reference。

例外情況

再來,有一個很重要的例外,如果我是用 object literal 的方式指定物件的值,那麼就會是 by value,那我們來看這個例外的情況:
img
在這裡,如果我們是用 object literal 的方式去定義 c 這個變數(如果不清楚什麼是 object literal,可參考:[筆記] JavaScript中的物件建立(Object) - Part 2),在這種情況底下,因為它並不清楚 c 的內容是已經存在的,所以它會建立一個新的記憶體位置來存放 c 物件裡面的內容。
img
若是使用 object literal 的方式來建立物件,則會變成 by Value,新增了一個記憶體的位置。

程式範例

// by value (primitives)
var a = 3;
var b;

b = a;
a = 2;
console.log("a is " + a);
console.log("b is " + b);


// by reference (all objects (including functions))
var c = {greeting : 'Hello'};
var d;
d = c;

console.log(c);
console.log(d);


c.greeting = "Hola";

console.log(c);
console.log(d);

// by reference (even as parameters)
function changeGreeting(obj){
  obj.greeting = 'Hi'; //mutate
}

changeGreeting(d);
console.log(c);
console.log(d);


var c = {greeting : 'Hello'};
var d;
d = c;
console.log(c);
console.log(d);

// equal operator sets up new memory space (new address)
c = {greeting: "Hola"};
console.log(c);
console.log(d);

JavaScript 不是 by value 也不是 by reference!?

這是最近在 fb 有很多人在討論的,最後大家的結論認為 JavaScript 實際上是另一種稱做 "by sharing" 的模式,只是對於原生值來說,看起來會很像 by value,對於物件來說則會很像 by reference。因此,如果對於這一部分有興趣的朋友們,或者在看完這篇文章後還是不很清楚 by value 和 by reference 的話,建議也可以閱讀下方的延伸閱讀。

延伸閱讀

資料來源

0 意見:

張貼留言