程式寫久了,不同的程式語言間除了語法不同外,感覺好像都差不多,除了各程式語言特別強調的功能。所以現在接觸到新的程式語言已經沒有特別的學習動力,都是安裝好執行環境、瞄一下範例就動手寫寫看,除非有特別的需求,已經很少去把程式語言的學習手冊掃一遍。要說沒有好好學習是一種錯,但人性卻總是常常這樣!
最近在寫程式時就出現一些需要釐清的觀念,到底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呼叫函數採用傳值呼叫!即使在呼叫時傳入物件,也只有修改物件屬性才會影響到外部的呼叫變數。所以,呼叫函數時有需要將運算跟處理資料結果儲存在參數時,就要傳入物件並儲存在對應的屬性欄位。
沒有留言:
張貼留言