借用紅寶書的一句話:
ECMAScript中所有函數的參數都是按值傳遞的
這個值如果是簡單類型,那麼就是其本身。如果是引用型別也就是物件傳遞的就是指向這個物件的位址。故我們可以認為參數傳遞全部都是值傳遞,那麼具體要怎麼理解呢?看下例子:
var obj = { n: 1 }; function foo(data) { data = 2; console.log(data); //2 } foo(obj); console.log(obj.n) // 1
先不說為什麼原因,我們就透過畫圖的方式來走一遍流程,我相信應該就能理解其中的參數傳遞了。切記傳遞引用類型傳遞的是指標!
先執行var obj = {n: 1};
,可以看成在堆疊的001位址中存入了一個指向 {n:1}
的指標*p
接下來為宣告function foo
此時會建立函數執行上下文,產生一個變數對象,其中聲明了形參data,由於函數沒有執行,目前值為undefined。我們記data位址為022。關於更多變數對象的知識可以參考冴羽老師的這篇JavaScript深入之變數對象,本文不深入研究關於AO相關,你只需要知道在宣告這個函數的時候裡面的形參已經被創建出來了。
執行foo(obj)
其中會進行參數傳遞,其中將obj中儲存的*p拷貝給處在022位址的data,那麼此時它們就指向了同一個對象,如果某一個變數更改了n的值,另一個變數中n的值也會更改,因為其中保存的是指針。
進入函數內部,順序執行data = 2;
此時002位址儲存了基本型別值,則直接儲存在堆疊中,從而與堆中的{n:1}失去了聯繫。從而印出console.log(data) // 2
,最後發現初始開啟的{n:1}物件沒有過更改,故而 console.log(obj.n) // 1
仍然列印1。
var obj = {n:1}; (function(obj){ console.log(obj.n); //1 obj.n=3; var obj = {n:2}; console.log(obj.n) //2 })(obj); console.log(obj.n) //3
整體來看這個範例中出現了同名覆蓋的問題。不太了解程式碼如何執行的流程,可能會因為同名的關係而有些混亂,不過沒關係。只要依照上一個例子的流程圖中的執行流程,一定可以得到正確的結果。
宣告變數obj,位址為011其中存入指向{n:1}的指標*p
宣告函數,雖然同為obj變數名,但是形參obj為AO中的屬性,不會與全域造成覆蓋,其擁有新的位址記作022,在未執行前其值為undefined 。
函數立即執行,此時將全域obj賦值給形參obj,我們忽略這個重複命名的問題,其實就是將011中的指標*p拷貝了一份給了022。同時執行第一個console.log(obj.n)
結果即為1。
執行obj.n=3
,此時為函數的形參即022中的obj來改變了物件內n的值。
最關鍵的一步:var obj = {n:2};
由於物件命名的關係可能許多童鞋就會有點懵,但依然按照同樣的方式來分析即可,由於使用了var那麼就是新聲明一個對象,從而會在棧中壓入新的地址記作033,其中存入了新的指針指向了新的物件{n:2}。從而之後印出的console.log(obj.n)
結果則應是新開闢的物件中的n的值。
最後列印 console.log(obj.n) //3
很顯然,全域的物件有過一次更改其值為3。
至此我們走完了上述兩段程式碼涉及變數的所有“心路歷程”,由於作者不是科班出身,這個圖中對於堆疊以及變數重名的描述可能不是非常的準確,有差錯的地方還望不吝賜教~重點是能理解我希望表達的意思就好。總的來說關鍵點就在於傳參的過程中存在一次值的拷貝,同時如果賦值物件是引用類型傳入的是指針,明白這兩點之後再加上之前流程圖的分析相信再遇到類似的問題都可以有較一致的思路了。
相關推薦:
以上是JavaScript參數傳遞圖解教程的詳細內容。更多資訊請關注PHP中文網其他相關文章!