JavaScript Weird Part (28) - 傳值與參考值

這一節又來到另一個重要觀念「傳值(by value)」與「參考值(by reference)」,這兩者都和變數關係密切。

傳值

好比說有一種純值(primitive value),可能是數字、布林或字串。我設定一個值到這個變數中,變數就有一個位址,它知道那個純值的記憶體位址。因為參考是參照到一個記憶體的位址。

設定一個新變數,設定它等於 a,也可以說是我傳入 a 到一個函數,兒這個函數的名稱是 b。如果這是 JavaScript 中的純值,b 這個新變數,將會指向一個新位址,一個新的記憶體地點。而那個純值的拷貝,被放到記憶體中的另一個地點。

所以如果 a 是 3,它在一個記憶體中的地點,接著 b 會指向 3,然後這個拷貝的地點,會被填上相同的值。這種方式叫做 by value>。意思是會傳入一個值是直接拷貝另一個值,這兩個變數就相等了。藉由拷貝一個直到另一個記憶體中的地點。

參考值

如果我在 JavaScript 裡面有一個物件,包含特殊類型的物件,像是函數。當我設定一個變數為物件,我仍然會得到一個記憶體位址,讓我可以參照到它。當 b 被設定等於 a,意思就是我要讓這兩個東西一樣,或者是把a 傳到函數裡面。這個新的變數 b,不會得到一個新的記憶體位址,而是指向 a的記憶體位址。沒有新的物件被創造,沒有物件的拷貝被創造。這兩個名稱都指向同一位址,就像有一個名字跟綽號一樣。這兩個名字都指向同一個地址的同一個人。ab 有相同的值,因為當你要取用它們的值,他們會指向相同的記憶體位址。這叫做 by reference

參考值和傳值是很不同的,很重要的是要了解所有物件都是 by reference 的,當用等號設定他們相等的時候。

1
2
3
4
5
6
7
// by value (primitive)
var a = 3
var b;
b = a
a=2;
console.log(a);
console.log(b);

這時會怎樣呢? a 會是 3 ,b 也是。但記住,這是傳值,因為 3 是一個純值、一個數值(其他純值也會有相同作用)。所以當b 被設定等於a 的時候,等號運算子看到這些是純值,就創造一個新的記憶體位址給b,拷貝a的值到b的地址。所以兩者都是 3 。但他們兩個是在不同的記憶體位址。

這表示我可以改變 a 的值,而不影響到 b,因為他們是記憶體中不同的 2 點。

1
2
3
4
5
6
7
// by reference (all objects include functions)
var c = {greeting:"hi"};
var d;
d = c;
c.greeting="hello" // mutate
console.log(c);
console.log(d);

mutate : 改變某件事情 (to change something)
如果深入看一些 JavaScript 文件,可能會看見”mutate a object”、”mutate a value”,可能是新增或移除屬性。
immutable : 不能被改變 (it can’t be changed)

等號運算子知道它們是物件,所以不會建立一個新的記憶體位址給 d,而是把 d 指向和 c 相同的記憶體位址。所以當我輸出這兩個東西,會看到同樣的值,但它們不是對方值的拷貝,它們只是指向同一個記憶體位址。我有一個物件在記憶體中,它有一個 greeting 屬性,我把它的值從 hi 改變為 hello,我改變 (mutate) 了它。

要記得,當處理物件的時候,你設定它等於另一個物件,你只是把兩個變數名稱指向同一個位址。

所以如果我改變那個物件的值,它用什麼名稱並不重要,它們只是別名而已。由於物件指向同一個位址,一旦你改變其中一個,另一個也跟著改變。

當我用函數的參數時也一樣。儘管是參數,物件也經由參考被傳入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// by reference (even as paremeters)

var c = {greeting:"hi"};
var d;
d = c;
c.greeting="hello" // mutate
console.log(c);
console.log(d);

function changeGreeting(obj){
obj.greeting ="hola"; // mutate
}

changeGreeting(d);

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

傳入 d 到函數,d 就是 obj,所以它被改變了。就像等號一樣,把一個物件傳入函數裡,表示它們是傳入它們的參考點 (by reference),所以obj 會指向d的記憶體位址,而d已經指向c 的記憶體位址。當它被改變,表示會更新這個物件所指向的記憶體位址裡面的值。

等號運算子設定一個新的記憶體空間

1
2
3
4
5
//equals operator sets up a new memory space (a new address)

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

這個例子中的等號,我設定 c 為一個新的值,等號運算子會設定一個新的記憶體空間給c,放進那個值,所以 dc就不再指向同一個記憶體位址。 d 仍然指向 hola。

這是一個特殊例子,這不是 by reference,因為等號運算子看到這些東西還不存在於記憶體裡面,它看到第二個參數不是已經存在的物件,它必須創造一個新的記憶體空間給物件,所以它建立一個新的空間,然後指向 c。在上個例子中我設定d 等於 cc已經存在了。但在這個例子中, c現在指向不同於d的記憶體位址。

小結

了解 by value 和 by reference 的差異很重要,因為在 JavaScript 裡面所有的純值都是 by value 而所有的物件都是 by reference。這在除錯時是非常重要的關鍵觀念。但是了解之後,就會知道當物件被改變了,那所有的物件、所有的變數,指向相同位址的東西都會被改變。所以屆時我們就可以了解,如果不小心無意改變了物件,那可能是傳入了這個物件的參考點而不自知。另一方面,改變一個純值,但原始的值並不會被改變,因為它是 by value。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2020 CYC'S BLOG All Rights Reserved.

UV : | PV :