[JS] 參數傳遞方式 Call by what?

在探討參數傳遞的機制前,先了解一點基本的計算機觀念,電腦中的資料需要有個空間存放起來,才能被拿出來操作(運算),這存放的地方就是『記憶體』,平常會聽到電腦的記憶體有 4G、8G…等,資料不會一次佈滿整個記憶體空間,記憶體會切成許多小區塊(位置)來使用,一般在探討時會用類似 0x010x02… 這樣的方法來表示記憶體位置(並非實際記憶體使用位置)。


💎 資料型別

JavaScript 的資料型別分成原始型別物件型別兩類,兩種型別的傳遞方式有所不同,下個段落繼續說明:

  • 原始型別(Primitives):string, number, boolean, null, undefined, symbol
  • 物件型別(Object):object, array, function…等都屬於物件型別

詳細說明可以參考我的另一篇文章: [JS] 深入了解型別與轉型


💎 差異比較

🔸 比較運算

  • 先看一段簡單的程式碼,使用 基本型別 做相等的比較運算

    1
    2
    3
    let a = 1;
    let b = 1;
    console.log(a === b); // true

    沒意外的結果是 true,記憶體的使用會像下圖:

    在一個記憶體存放值,變數名稱 a 指向這個值的記憶體位置
  • 接著改成 物件型別 進行比較

    1
    2
    3
    let obj1 = {a: 1};
    let obj2 = {a: 1};
    console.log(a === b); // false

    結果卻變成 false 了,此時記憶體的使用情況變成下圖:

    變數裡面存放了一個指向物件實體的記憶體位置,所以這兩個變數的內容實際是不同的記憶體位置,比較的結果是不相等

🔸 拷貝變數

  • 基本型別 範例程式:將 c 的資料賦予到變數 d,再改變 c 的值

    1
    2
    3
    4
    5
    6
    let c = 1;
    let d = c;
    c = 2;

    console.log(c); // 2
    console.log(d); // 1

    c 的值修改之後, d 沒有同時變動,由此可知 c 的值被『複製』給 d 了,兩個變數指向不同的記憶體位置,這時候記憶體的變化如下圖:

  • 使用 物件型別 來做相同的操作

    1
    2
    3
    4
    5
    6
    7
    let 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)

延伸閱讀: Andy - 關於JS中的淺拷貝(shallow copy)以及深拷貝(deep copy)


💎 Call by sharing?

  • 如果仔細看會發現上一個段落名稱是段落小結,沒錯,事情還沒結束!接著來看下面這段程式碼,並想想結果會印出什麼?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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 物件內的值。

    到了 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?


以上是我對這參數傳遞的一點認知,如有錯誤或是補充的知識點,也歡迎大家不吝指教,謝謝!