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
型別為基本型別,當我們比較 a
與 b
時,是互相比較值,所以是 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
相同的位址。
所以當我們新增或修改 obj1
與 obj3
,雙方都會變動。
// 承接上述程式碼
obj1 = {};
console.log( obj1 ); // {}
console.log( obj3 ); // {v1: 1,v2:2}
當我們重新將 ob1
指向新的物件時,產生了新的記憶體位置。
obj3
並不會更著變動,是因為 obj3
還是指向原先的物件位址,與 obj1
無關。
像這種情況就稱作為「傳址」Pass by reference。
結論
- 變數指派的值為基本型別(
number
、string
、boolean
、null
、undefined
、symbol
),那麼賦值是「傳值」。 - 變數指派的值為物件型別(
object
、array
、fuction
),那麼賦值是「傳址」。
另外關於「JavaScript 其實是 Pass by sharing 」或是「JavaScript 只有 Pass by value」的論點,我覺得並不重要,這些只是定義上或名詞解釋的不同罷了,最重要的是理解執行什麼操作會有什麼行為上的表現。