💎 資料的家 - 記憶體
在探討參數傳遞的機制前,先了解一點基本的計算機觀念,電腦中的資料需要有個空間存放起來,才能被拿出來操作(運算),這存放的地方就是『記憶體』,平常會聽到電腦的記憶體有 4G、8G...等,資料不會一次佈滿整個記憶體空間,記憶體會切成許多小區塊(位置)來存放,一般在探討時會用類似 0x01
、0x02
... 這樣的方法來表示記憶體位置(並非實際記憶體使用位置)。
💎 基本型別、物件型別
JavaScript 的資料型別共有7種,並且可以分成基本型別
和物件型別
兩類,這兩種資料型別在傳遞時方式有所不同:
🔸 基本型別(Primitives)
- string
- number
- boolean
- null
- undefined
- symbol (ES 6 新定義)
🔸 物件型別(Object)
以下都屬於 object
- object
- array
- function
💎 兩大類型的差異
🔸 比較運算
先看一段簡單的程式碼,使用基本型別資料,比較是否相等
let a = 1;
let b = 1;
console.log(a === b); // true
沒意外的結果是 true ,從下圖可以看到記憶體位置內直接存放『值』,將兩個值做比較得到相等的答案。
接著改成物件型別資料,再次進行比較
let obj1 = {a: 1};
let obj2 = {a: 1};
console.log(a === b); // false
結果卻變成 false 了,從下圖可以看到變數裡面存放的變成了一個指向物件實體的記憶體位置,所以兩個不同的記憶體位置比較的結果是不相等。
🔸 拷貝變數
下面這段程式將 c 的基本型別資料賦予到變數 d,再改變 c 的值
let c = 1;
let d = c;
c = 2;
console.log(c); // 2
console.log(d); // 1
c 的值修改之後, d 沒有同時變動,由此可知 c 的值被『複製』給 d 了,兩個變數指向不同的記憶體位置,這時候記憶體的變化如下圖:
接著改成把物件型別資料賦予到另一個變數,再改變原來的物件內容
let obj3 = {a: 1};
let obj4 = obj3;
obj3.a = 2;
console.log(obj3); // {a: 2}
console.log(obj4); // {a: 2}
console.log(obj3 === obj4); // true
這次的結果是兩個物件內的值是連動的,表示第二個變數並沒有複製整個物件,而是指向同一個記憶體位置,用比較運算也得到相等的結果,如下圖:
💎 段落小結
基本型別
的資料特性是,在記憶體內會存放純粹的『值』,這個值是靜態的、不可變的(immutable),在傳遞時會以新的記憶體空間來存放新的(或複製的)資料,這種參數傳遞模式通常被稱為傳值(call by value)
。物件型別
的資料是動態的、可變的(mutable),變數名稱內會存放一個記憶體位置指向物件的本體,就像是這個物件的聯絡窗口一樣,無論新增、修改、刪除物件內的屬性值,都是透過這個記憶體位置去找到實體物件後再處理,而做賦值運算時只會指向這個記憶體位置,而不是將他底下的物件全部複製過去(這種賦值方式又稱為淺拷貝),這種參數傳遞模式通常被稱為傳參考(call by reference)
。
💎 Call by sharing?
如果仔細看會發現上一個段落名稱是段落小結
,沒錯,事情還沒結束!
接著來看下面這段程式碼,並想想結果會印出什麼
function 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 裡面的 a 屬性改為 2。
但是到了obj = { b: 3 }
的時候卻是以傳值的方式進行,所以新增了一個記憶體位置存放物件,再回傳給 objB。
函式
確實會依傳入的參數型別有所不同(基本型別傳值、物件型別傳參考);但是對參數做賦值運算時(=)
,就會指向新的記憶體位置
,這種模式常被稱為 call by sharing
。
💎 總結
目前並沒有一個明確的定義去認定這些資料傳遞方式的名詞所代表的行為是什麼(可以參考下方 胡立 大大的文章),無論是 call by value、reference 或 sharing,甚至是其他語言的 address、pointer,亦或是要說 call by 還是 pass by 其實並沒有這麼重要,最重要的是了解 JavaScript 在處理資料時有什麼不同的機制,讓我們在撰寫時不要踩坑,才是實際又有幫助的。
以上是我對這參數傳遞的一點認知,如有錯誤或是補充的知識點,也歡迎大家不吝指教,謝謝!
參考資料:
Huli - 深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?