JavaScript 的「傳值」與「傳址」

「傳值」與「傳址」這部分在 JavaScript 是相當重要的觀念,正確了解這個觀念能避免發生不必要的錯誤。

同步發表於HackMD:JavaScript 的「傳值」與「傳址」

目錄


名詞

傳值:Call by value 或是 Pass by value。
傳址或是傳參考:Call by reference 或是 Pass by reference。


傳值與傳址

在其他程式語言有語法可以決定要傳值還是傳參考,但在 JavaScript 中沒有選擇,基本型別就是使用傳值,物件型別就是使用傳址。

  • 傳值 by value:當我們創造變數並給值(基本型別 primitive type)時,這個變數會指向值在電腦記憶體中的位置,若我們以這個值為參照,指定另一個變數指向這個值時,電腦會在記憶體中新增(複製)一個新值,讓後來的這個變數指向新的值。

  • 傳址 by reference:當我們創造變數並給值(物件型別 Obeject)時,變數會指向物件在電腦記憶體中的位置,若我們以這個物件為參照,指定另一個變數指向這物件,這個變數就會指向電腦記憶體中同樣的物件,不會有新的物件在記憶體中被創造出來。

1. 傳值

當值的型別為基本型別(primitive type),那麼永遠都是藉由值的新增(或稱複製)來指定(或稱傳遞)值。

來看程式碼說明:

var a = 10;
var b = 10;

console.log( a === b );  // true

var c = a;
c++;

console.log( a );   // 2
console.log( c );   // 3

由於 10 型別為基本型別,當我們比較 ab 時,是互相比較值,所以是 true

c 指定為 a 由於 a 的值是基本型別,所以會 c 得到的是 a 的值而不是 a 的記憶體位址。

因此 c 就算改變了,a 也不會受到影響,兩者是獨立的。

像這種情況就稱作為「傳值」Pass by value。

2. 傳址

var obj1 = { v1: 1 };
var obj2 = { v1: 1 };

console.log( obj1 === obj2 );  // false

var obj3 = obj1;

console.log( obj1 === obj3 );  // true

obj1.v1 = 0;
obj3.v2 = 2;

console.log( obj1.v1 );  // 0
console.log( obj3.v1 );  // 0
console.log( obj1.v2 );  // 2
console.log( obj3.v2 );  // 2

看到 obj1 === obj2,對於非原型物件即使有相同的值,兩物件相互比較也會是 false,因為每個物件都是獨立存在的實體,兩者的記憶體位置並不相同。

再來將 obj3 透過 obj3 = obj1 的方式賦值,透過 === 去檢查兩者實體時,你會發現兩者實際上是同一個,因為並沒有新的物件被新增(或複製)出來,而是 obj3 被指向與 obj1 相同的位址。

所以當我們新增或修改 obj1obj3,雙方都會變動。

// 承接上述程式碼

obj1 = {};

console.log( obj1 );  // {}
console.log( obj3 );  // {v1: 1,v2:2}

當我們重新將 ob1 指向新的物件時,產生了新的記憶體位置。

obj3 並不會更著變動,是因為 obj3 還是指向原先的物件位址,與 obj1 無關。

像這種情況就稱作為「傳址」Pass by reference。


結論

  • 變數指派的值為基本型別(numberstringbooleannullundefinedsymbol),那麼賦值是「傳值」。
  • 變數指派的值為物件型別(objectarrayfuction),那麼賦值是「傳址」。

另外關於「JavaScript 其實是 Pass by sharing 」或是「JavaScript 只有 Pass by value」的論點,我覺得並不重要,這些只是定義上或名詞解釋的不同罷了,最重要的是理解執行什麼操作會有什麼行為上的表現。


相關資訊

TOP