JS基本觀念:call by value 還是reference 又或是 sharing?

Image for post
Image for post

這是篇用來介紹 javascript 的基礎觀念的文章。希望能透過一些簡單例子,讓看的人能夠輕易了解原理。同時也是對自己的一個學習過程紀錄。並且鍛鍊自己講故事的能力。

Data Type

首先,在javascript裡面,有許多種資料型別(data type),但主要分兩大類,一種是原始型別(primitive type),另一種是物件(Object)。

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt
  • String
  • Symbol(於 ECMAScript 6 新定義)
  • Primitive type以外的,例如array, function, map …etc

一般來說,primitive type會是call by value,而object則是call by reference。以下會用幾個簡單例子來描述。

Call by value

var x = 1;
var y= "test";
var a = x;
var b = y;
a = 2;
b = "xyz";
console.log(x, y, a, b) // -> 1, "test", 2, "xyz"

從上面例子可看到, 當a = x時,實際上是把value copy給a,所以接著的a & x都是獨立的,操作不互相影響。

Call by Reference

var ref1 = [1];
var ref2 = ref1;
ref1.push(2);
console.log(ref1, ref2); // -> [1, 2], [1, 2]

當ref1被宣告時,javascript會在記憶體的某處建立一個object,並將ref1指(reference)到這個object。

Image for post
Image for post

接著var ref2 = ref1 這一行,就是讓ref1把相同的reference傳給ref2。 此時兩個變數都是指向同個object了,因此對這個object操作都會同時影響到ref1 & ref2。

Image for post
Image for post

`AND` OPERATION

中場休息,穿插一點對reference的延伸情境,=====這兩種等於如果被用在reference type的變數做比較,它則會比較reference而非value。

var arrRef = ['Hi!'];
var arrRef2 = arrRef;
console.log(arrRef === arrRef2); // -> true

相同reference,故true。

var arr1 = ["Hi!"];
var arr2 = ["Hi!"];
console.log(arr1 === arr2); // -> false

不同reference,所以即使內容相同也會false。

若要比較object的內容是否相同,可將其轉換成string來做比較!

var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);
console.log(arr1str === arr2str); // -> true

Call by sharing

上面提到的傳值跟傳址對一般開發者來說應該都很稀鬆平凡,接著我們看下面的傳參數到function的例子。

function changeAge(person) {
person.age = 25;
return person
}
var personObj1 = {
name: 'Charles',
age: 30
};
var personObj2 = changeAge(personObj1);console.log(personObj1);
console.log(personObj2);

經過上面的call by reference練習後,這題答案應該很明顯。

{ name: 'Charles', age: 25 }
{ name: 'Charles', age: 25 }

那麼,對function稍做修改後,再試一次。

function changeAge(person) {
person.age = 25;
person = {
name: 'John',
age: 50
}
return person
}
var personObj1 = {
name: 'Charles',
age: 30
};
var personObj2 = changeAge(personObj1);console.log(personObj1);
console.log(personObj2);

這題的答案是

{name: 'Charles', age: 25}
{name: 'John', age: 50}

你可能會覺得很奇怪,不是說好的call by reference麼,怎麼不是內外都是跟著修改成John呢?

既不是 call by value 也不是 call by reference,那這樣應該叫做什麼呢?

有人把這種方式叫做 call by sharing(還沒找到適合的翻譯),意思就是我們讓 function 裡面的那個 person 跟外面的 personObj1「共享」同一個 object,所以透過裡面的 person,你可以去修改「共享到的那個 object」的資料。

上面跟之前的 call by reference例子看起來沒兩樣,唯一的差異是如果你在 function 裡面把 person 重新賦值,就代表你要讓這個 obj 指向一個新的 object,這樣變成內部跟外部指向不同的Object,所以外面的 personObj1 會保留原本的值。

Image for post
Image for post

再舉一個相同的例子,都是強調重新賦值後,會指向新的Object,從此跟過去在無任何瓜葛!

var personObj1 = {
name: 'Alex',
age: 30
};
var person = personObj1;
person.age = 25;
person = {
name: 'John',
age: 50
};
var personObj2 = person;
console.log(personObj1); // -> { name: 'Alex', age: 25 }
console.log(personObj2); // -> { name: 'John', age: 50 }

結論:如果先不管名詞定義到底為何(有的人認為只有call by sharing),先以行為來分類的話,primitive type基本上就是call by value了,而object的話,基本上是call by reference,唯一要注意的就是重新賦值這個動作造成reference改變的影響。

文章到此結束,希望能給看到的人一些幫助,也歡迎對內容有疑問的留言給我,感謝!

參考資料

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Data_structures

Written by

Made in Taiwan的後端工程師,擅長nodejs做後端開發。相信分享與交流可以讓世界更美好,加上自己有點金魚腦,所以開始了寫些有的沒的之路XD

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store