2018年9月25日 星期二

JavaScript的函數是傳值還是?

程式寫久了,不同的程式語言間除了語法不同外,感覺好像都差不多,除了各程式語言特別強調的功能。所以現在接觸到新的程式語言已經沒有特別的學習動力,都是安裝好執行環境、瞄一下範例就動手寫寫看,除非有特別的需求,已經很少去把程式語言的學習手冊掃一遍。要說沒有好好學習是一種錯,但人性卻總是常常這樣!

最近在寫程式時就出現一些需要釐清的觀念,到底JavaScript的函數呼叫是傳值(Call By Value)還是傳參考(Call By Reference)?之前因為都沒有需要針對傳入的參數進行修改,所以就沒有在意這個問題。


題外話,以前學C語言時,傳值/址可說是搞死不少人,&、*這兩個符號在C語言裡是用的最兇的。回想起來,C語言並沒有所謂的傳參考,因為送到函數之前就要取位址,收參數的函數就要用對應的資料型態記錄位址。雖然是很惱人的語法,但了解用法的人就會覺得這種語法的迷人之處。從硬體控制的角度來說,傳址仍舊是相當實用的語法。



回到JavaScript的函數到底是傳值(Call By Value)還是傳參考(Call By Reference),可以參考「JavaScript中參數的傳值與傳址心得 - 布丁布丁吃什麼?」,裡頭的許多討論、回覆很具參考價值。其中有一段提到「JavaScript的本質是傳值而非傳址」,其中也提到可以參考「JavaScript Function Parameters」的內文介紹,最後兩段文字的內容如下:

Arguments are Passed by Value

The parameters, in a function call, are the function's arguments.

JavaScript arguments are passed by value: The function only gets to know the values, not the argument's locations.

If a function changes an argument's value, it does not change the parameter's original value.

Changes to arguments are not visible (reflected) outside the function.

大意就是:函數呼叫的參數採用傳值方式。紅色底線說明在函數內修改參數內容不會影響到外部。

Objects are Passed by Reference

In JavaScript, object references are values.

Because of this, objects will behave like they are passed by reference:

If a function changes an object property, it changes the original value.

Changes to object properties are visible (reflected) outside the function.

大意就是:物件採用傳參考方式。紅色底線說明在JavaScript語言裡物件參考是一個值(這個翻譯連自己也覺得怪)。但接續的說明比較有意思,改變傳入物件屬性資料會影響到原本的物件屬性資料。

關於這類的討論在網路上可以查到一大堆,就不再口水了,這篇文章只純粹記錄程式碼與執行結果對照,讓自己忘了的時候可以看一下。執行環境採用Rhino,應該跟瀏覽器的執行環境沒有差別。

底下是字串資料型態、字串物件、數值資料型態、數值物件傳入函數並修改後的程式碼與結果:

try {

  function changeParam(fs1, fs2, fn1, fn2) {

    print('========== in function ==========');

    print('s1 type:' + typeof fs1);
    print('s2 type:' + typeof fs2);
    print('n1 type:' + typeof fn1);
    print('n2 type:' + typeof fn2);
  
    fs1 += '-append';
    fs2 += '-append';
    fn1 += 2;
    fn2 += 2;
  
    print('s1:' + fs1);
    print('s2:' + fs2);
    print('n1:' + fn1);
    print('n2:' + fn2);
    print('');
  }

  var s1 = 's1';
  var s2 = new String('s2');
  var n1 = 10;
  var n2 = new String(10);

  print('========== Before execute function ==========');
  print('s1 type:' + typeof s1);
  print('s2 type:' + typeof s2);
  print('n1 type:' + typeof n1);
  print('n2 type:' + typeof n2);
  print('s1:' + s1);
  print('s2:' + s2);
  print('n1:' + n1);
  print('n2:' + n2);
  print('');

  changeParam(s1, s2, n1, n2);

  print('========== After execute function ==========');
  print('s1:' + s1);
  print('s2:' + s2);
  print('n1:' + n1);
  print('n2:' + n2);
}
catch (error) {

  print('Error message: ' + error.message);

  quit(1);
}
finally {

}

執行結果:




無論傳入函數的是基本資料型態或物件型態,在函數中對參數的運算結果都不會影響到原本傳入的變數資料,也就是所謂的傳值呼叫。

再看底下這個例子,修改物件屬性資料:

try {

  function changeParam(fo) {

    print('========== in function ==========');
  
    print('fo type:' + typeof fo);
  
    fo.name += '-append';
    fo.age += 2;

    print('o.name:' + fo.name);
    print('o.age:' + fo.age);
    print('');
  }

  var o = {

    "name": "ABC",
    "age": 24
  };
 
  print('========== Before execute function ==========');
  print('o type:' + typeof o);
  print('o.name:' + o.name);
  print('o.age:' + o.age);
  print('');

  changeParam(o);

  print('========== After execute function ==========');
  print('o.name:' + o.name);
  print('o.age:' + o.age);

  quit(1);
}
catch (error) {

  print('Error message: ' + error.message);

  quit(1);
}
finally {

}

執行結果:



在函數內修改物件屬性資料會影響到原本傳入的物件屬性資料。

結論:JavaScript呼叫函數採用傳值呼叫!即使在呼叫時傳入物件,也只有修改物件屬性才會影響到外部的呼叫變數。所以,呼叫函數時有需要將運算跟處理資料結果儲存在參數時,就要傳入物件並儲存在對應的屬性欄位。

沒有留言:

張貼留言

Related Posts Plugin for WordPress, Blogger...