[JS] 參數傳遞方式 Call by what?
在探討參數傳遞的機制前,先了解一點基本的計算機觀念,電腦中的資料需要有個空間存放起來,才能被拿出來操作(運算),這存放的地方就是『記憶體』,平常會聽到電腦的記憶體有 4G、8G…等,資料不會一次佈滿整個記憶體空間,記憶體會切成許多小區塊(位置)來使用,一般在探討時會用類似 0x01
、0x02
… 這樣的方法來表示記憶體位置(並非實際記憶體使用位置)。
💎 資料型別
JavaScript 的資料型別分成原始型別
和物件型別
兩類,兩種型別的傳遞方式有所不同,下個段落繼續說明:
- 原始型別(Primitives):string, number, boolean, null, undefined, symbol
- 物件型別(Object):object, array, function…等都屬於物件型別
詳細說明可以參考我的另一篇文章: [JS] 深入了解型別與轉型
💎 差異比較
🔸 比較運算
先看一段簡單的程式碼,使用
基本型別
做相等的比較運算1
2
3let a = 1;
let b = 1;
console.log(a === b); // true沒意外的結果是 true,記憶體的使用會像下圖:
在一個記憶體存放值,變數名稱 a 指向這個值的記憶體位置 接著改成
物件型別
進行比較1
2
3let obj1 = {a: 1};
let obj2 = {a: 1};
console.log(a === b); // false結果卻變成 false 了,此時記憶體的使用情況變成下圖:
變數裡面存放了一個指向物件實體的記憶體位置,所以這兩個變數的內容實際是不同的記憶體位置,比較的結果是不相等
🔸 拷貝變數
基本型別
範例程式:將 c 的資料賦予到變數 d,再改變 c 的值1
2
3
4
5
6let c = 1;
let d = c;
c = 2;
console.log(c); // 2
console.log(d); // 1c 的值修改之後, d 沒有同時變動,由此可知 c 的值被『複製』給 d 了,兩個變數指向不同的記憶體位置,這時候記憶體的變化如下圖:
使用
物件型別
來做相同的操作1
2
3
4
5
6
7let obj1 = {a: 1};
let obj2 = obj1;
obj1.a = 2;
console.log(obj1); // {a: 2}
console.log(obj2); // {a: 2}
console.log(obj1 === obj2); // true修改第一個物件變數的內容,第二個物件變數也跟著變動了,表示兩個物件變數都指向同一個記憶體位置,用比較運算也得到相等的結果,如下圖:
💎 段落小結
基本型別
是純粹的『值』,這個值是靜態的、不可變的(immutable),在傳遞時會以新的記憶體空間來存放新的(或複製來的)資料,這種參數傳遞模式通常被稱為傳值(call by value)
。物件型別
的資料是動態的、可變的(mutable),變數內會存放一個記憶體位置指向物件的本體,就像是這個物件的經紀人一樣;因此,做賦值運算時只會取得這個記憶體位置,而不是物件實體,這種複製方式又稱為淺拷貝,而參數傳遞模式通常被稱為傳參考(call by reference)
。
💎 Call by sharing?
如果仔細看會發現上一個段落名稱是
段落小結
,沒錯,事情還沒結束!接著來看下面這段程式碼,並想想結果會印出什麼?1
2
3
4
5
6
7
8
9
10
11function share(obj) {
obj.a = 2;
obj = { b: 3 };
return obj
}
let objA = { a: 1 };
let objB = share(objA);
console.log(objA); // { a: 2 }
console.log(objB); // { b: 3 }以物件傳參考的邏輯來看,objA 裡面存的記憶體位置會傳入函式,在
obj.a = 2
這段也確實透過傳入的位址成功修改 objA 物件內的值。到了
obj = { b: 3 }
這段卻沒有改變原始的物件(objA),而是以傳值的方式進行,新增了一個記憶體位置存放物件,再透過 return 回傳給 objB。函式
會依傳入的參數型別有所不同(基本型別傳值、物件型別傳參考),但是對參數做賦值運算時(=)
,就會指向新的記憶體位置
,這種模式常被稱為call by sharing
。
💎 總結
- JavaScript 並沒有正式文件去定義該怎麼稱呼這些資料傳遞方式,無論是 call by value、reference 或 sharing,亦或是要說 call by 還是 pass by 其實並沒有這麼重要,最重要的是了解 JavaScript 在處理資料時有什麼不同的機制,讓我們在撰寫時不要踩坑,才是實際又有幫助的。
參考資料: Huli - 深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?
以上是我對這參數傳遞的一點認知,如有錯誤或是補充的知識點,也歡迎大家不吝指教,謝謝!