JavaScript中參數的傳值與傳址心得
JavaScript中參數的傳值與傳址心得
JavaScript中參數傳遞到底是「傳值」(passing by value) 還是「傳址」(passing by refernce) ,似乎常常造成許多人混亂。我自己原本也一直以為JavaScript只是傳值而非傳址,而為此混亂了好一陣子。特別是在撰寫物件導向的JavaScript程式時更容易造成混亂。
有人認為JavaScript的函數(function)會依據你輸入參數的資料類型來區分傳值還是傳址,物件(object)的話會使用傳址,非物件的則是使用傳值。但實際上,我認為JavaScript的本質是傳址而非傳值。而端看你在函數裡面是否修改參數的記憶體位址(也就是建立新的資料給參數),來決定是否影響函數之外的參數資料。
以下我們就來一一地檢視一下JavaScript究竟是傳值或是傳址的參數傳遞運作方式吧。
2016/1/14更新:根據底下留言網友討論指出了我原文的錯誤,可以發現其實JavaScript本質仍是「傳值而非傳址」。雖然下面文章仍放著給大家參考,但下面的討論更有看頭。希望觀看這篇的讀者能夠連下面的討論一併閱讀,方能深入瞭解JavaScript的奧妙。
參數傳值:Number、String、Boolean等非Object類型
當輸入給函數的參數類型為非物件(object)的資料型態,例如Number(數字)、String(字串)或是Boolean(布林邏輯值)的時候,JavaScript的函數會以傳值的方式來處理。傳值的意思是說,輸入到函數裡面的參數會被複製一份,而在函數裡面對參數的操作,不會影響到外在參數的影響。以下是一段示範用的程式碼:
<script type="text/javascript"> //宣告變數,資料類型為字串 var paramA = 'original'; //確認變數資料 document.write(paramA + '<br />'); //輸出 original //定義函數,在函數中修改參數資料,並且輸出做確認 function changeParamA(paramA) { //修改參數 paramA = 'changed'; //確認被修改之後的參數資料 document.write(paramA + '<br />'); //輸出 changed } //在函數中修改參數 changeParamA(paramA); //在函數中運作,輸出 changed //被函數修改之後,仍然維持原本的資料 document.write(paramA + '<br />'); //輸出 original </script>
上面的例子的輸出結果將會是:
original changed original
由此可知,JavaScript函數中使用參數為字串資料類型時,將會是以傳值的方式運作。同樣的道理也適用於數字、布林值上。
參數傳址:Object
當輸入給函數的參數資料類型為Object(物件)時,JavaScript會以傳址的方式來處理。傳址的意思是說,輸入的函數裡面的參數只是「記憶體中的位置」,而在函數裡面對參數的操作,其實是對記憶體位置中的該物件進行修改,因此函數之外的參數也會受到影響。以下是一段示範用的程式碼:
<script type="text/javascript"> //宣告變數,資料類型為物件 var paramB = { attr: 'original' }; //確認變數資料 document.write(paramB.attr + '<br />'); //輸出 original //定義函數,在函數中修改參數資料,並且輸出做確認 function changeParamB(paramB) { //修改參數。注意修改的方式,是直接指定物件的屬性進行修改,而非建立新的物件。 paramB.attr = "changed"; //確認被修改之後的參數資料 document.write(paramB.attr + '<br />'); //輸出 changed } //在函數中修改參數 changeParamB(paramB); //在函數中運作,輸出 changed //被函數修改之後,物件的屬性也跟著改變了 document.write(paramB.attr + '<br />'); //輸出 changed </script>
上面例子中輸出的結果將會是:
original changed changed
在上面的例子中,你可以發現到以物件資料型態輸入函數中的參數,在函數中被修改之後,在函數之外也會跟著受到影響,一般來說這會被視為傳址的參數傳遞方式。
再探Object的參數傳遞
在上一節介紹傳址的例子中,函數修改參數的方式是指定物件的屬性進行修改。但是如果函數修改參數的方式是直接指定新的物件、或是輸入其他的資料類型,那麼函數之外的參數就不會受到修改,也就是傳值的參數傳遞。以下是一段示範用的程式碼:
<script type="text/javascript"> //宣告變數,資料類型為物件 var paramC = { attr: 'original' }; //確認變數資料 document.write(paramC.attr + '<br />'); //輸出 original //定義函數,在函數中修改參數資料,並且輸出做確認 function changeParamC(paramC) { //修改參數。注意修改的方式,是建立新的物件,而新的物件裡面也包含attr屬性。 paramC = { attr: 'changed' }; //確認被修改之後的參數資料 document.write(paramC.attr + '<br />'); //輸出 changed } //在函數中修改參數 changeParamC(paramC); //在函數中運作,輸出 changed //被函數修改之後,仍然維持原本的資料 document.write(paramC.attr + '<br />'); //輸出 original </script>
上面例子中輸出的結果將會是:
original changed original
你會發現到,即使輸入參數的資料類型為物件,但是當你在函數中為參數指定新的資料的時候,函數之外的參數並不會受到影響,是為傳值參數傳遞的運作方式。
推測JavaScript是傳址方式運作
因此由以上的例子中,我可以得到一個推測的結論:JavaScript一直都是用傳址的參數傳遞。只是根據函數對於參數是否變更參數的記憶體位址,而會有看起來像是傳值或傳址的運作差異。在以物件傳遞、修改物件屬性的paramB例子中,函數本身並沒有修改參數的記憶體位址,也就是沒有給予paramB新建立的資料(新的資料就是一個新的記憶體位址),因此在函數中被修改的paramB,函數之外也會受到影響。
而paramA跟paramC的例子裡都是建立了新的資料給參數,修改了參數本身的記憶體位址,讓函數裡面運作的參數跟函數之外兩者互不相干,因此乍看之下就類似傳值的運作方式。
參數傳值或是傳址運作:Array
那麼我們再回頭來看看JavaScript中一種特殊的資料型態:Array(陣列)。陣列本質上屬於「Object」,那麼把陣列作為參數輸入函數時,究竟是傳值還是傳址呢?由前面的推論中可知,這是根據函數裡面對於參數的操作是否參數為建立新的資料來判斷。以下是一段示範用的程式碼:
<script type="text/javascript"> //宣告變數,資料類型為陣列 var paramAry = ['original']; //確認變數資料 document.write(paramAry[0] + '<br />'); //輸出 original //定義函數,在函數中修改參數資料,並且輸出做確認 function changeParamAry1(paramAry) { //修改參數。注意修改的方式,是建立新的陣列。 paramAry = ['changed']; //確認被修改之後的參數資料 document.write(paramAry[0] + '<br />'); //輸出 changed } function changeParamAry2(paramAry) { //修改參數。注意修改的方式,是修改陣列裡面的索引,而非建立新的陣列。 paramAry[0] = 'changed'; //確認被修改之後的參數資料 document.write(paramAry[0] + '<br />'); //輸出 changed } //在函數中修改參數 changeParamAry1(paramAry); //在函數中運作,輸出 changed //被函數修改之後,仍然維持原本的資料 document.write(paramAry[0] + '<br />'); //輸出 original //在函數中修改參數 changeParamAry2(paramAry); //在函數中運作,輸出 changed //被函數修改之後,參數的資料已經被修改 document.write(paramAry[0] + '<br />'); //輸出 changed </script>
上面例子中輸出的結果將會是:
original changed original changed changed
因此你可以發現到,根據函數對於參數的操作,就會造成傳值或傳址的運作差異。
結語
當JavaScript越寫越複雜之後,對於參數的傳值或傳址就會越來越注重。也許是我讀的JavaScript的書不夠多,像這種基礎的概念居然都不是從書上得來,而是在網路上找尋各個程式設計師的心得與探討之後,再整理出來的。題外話,因為這篇主要是整理理論的東西,沒什麼圖片。秉持著一篇文章一定要有一張圖的精神,就去ICON FINDER找了張JavaScript的圖示來貼在開頭XD 而最近都在談JavaScript的東西,因此我也為這個Blog加入了JavaScript的分類標籤。之前的發文就待有空時再來整理歸類到JavaScript標籤去吧。
參考資源
- Snook, J. (2006, September 14). JavaScript: Passing by Value or by Reference. Snook.ca. Retrieved September 30, 2010, from http://snook.ca/archives/javascript/javascript_pass
- 劍俠鼻唄. (2006, June 23). 傳值呼叫VS.傳址呼叫測試(JavaScript). 小調過三丁...步出地獄の風采. Retrieved September 30, 2010, from http://beba-brook.blogspot.com/2006/06/vs.html
Comments