2016年3月13日 星期日

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

圖片來源: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)。


然後,我們把a和b呼叫出來,你猜猜看會得到什麼值:


這時候,我們會發現a的值變成了2,而b的值仍然是3,感覺好像蠻直觀的對吧。

可是,奇妙的地方讓我們繼續看下去:

我們一樣先建立一個變數c,它是一個物件;接著建立變數d,讓d = c;最後我把物件c裡面的值做一下修改,從Hello改成Hola;最後再把結果顯示出來。


你認為結果會是什麼呢?


看出有什麼特別之處了嗎?

在第一個例子中,我們將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的變數

By Value的情況。
圖片來源
[Udemy] JavaScript: Understanding the Weird Parts

By Reference

在來看一下By Reference。

當我將變數a設立成一個Object(或function)時,這時候,一樣會在記憶體中給它一個位置(假設叫做0x001);但是當我建立一個變數b,並且把變數b的值等同於a時,這時候並不會再給它一個新的位置,而是一樣指定到物件a的位置(即0x001),實際上是不會有新的東西被建立,變數a和b都會被指稱到相同的位置(即0x001),因此,當a的值改變的時候b的值也會改變,因為它們實際上是指稱到相同的位置,這種情形我們就稱為By Reference,這樣情況會發生在Object和Function這種變數。

By Referecne的情況。
圖片來源[Udemy] JavaScript: Understanding the Weird Parts

經過這樣的說明,我想你就知道在上面的例子中,為什麼第一個例子改變(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屬性的值。



在這個例子中,我們可以看到,原本c和d物件中屬性greeting的值都是Hello,後來因為我修改了c.greeting的值為Hola,所以c和d物件屬性greeting的值都變成了Hola,最後我利用function的方式,改變d修改了d.greeting的值為Hi,所以到了最後c和d物件屬性greeting的值都變成了Hi。這就是因為By reference的緣故!


例外情況


再來,有一個很重要的例外,如果我是用object literal的方式指定物件的值,那麼會是by value,那我們來看這個例外的情況:


在這裡,如果我們是用object literal的方式去定義c這個變數(如果不清楚什麼是object literal,可參考:[筆記] JavaScript中的物件建立(Object) - Part 2),在這種情況底下,因為它並不清楚c的內容是已經存在的,所以它會建立一個新的記憶體位置來存放c物件裡面的內容。


若是使用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的話,建議也可以閱讀下方的延伸閱讀。
資料來源
[Udemy] JavaScript: Understanding the Weird Parts- Conceptual Aside: By Value vs By reference
Share:

0 意見:

張貼留言