摘要:講清楚之參數傳值參數傳值是指函數調用時,給函數傳遞配置或運行參數的行為,包括通過進行傳值。所以對的賦值會改變上下文棧中標識符保存的具體值此時如果使用的是按引用傳遞,則變量所指向的對象因該也被賦值為。
講清楚之 javascript 參數傳值
參數傳值是指函數調用時,給函數傳遞配置或運行參數的行為,包括通過call、apply 進行傳值。
在實際開發中,我們總結javascript參數傳值分為基本數據類型按值傳遞(String、Numbe、Boolean、Null、undefind),引用數據類型按引用傳遞(Object, 包括Array、Function、Data)。這篇文章將要糾正這一誤解: 實質上引用類型是按共享傳遞的。
在探索傳值問題前,我們先看一下 javascript 在聲明變量時是怎樣分配內存的
原始值:存儲在棧(stack)中的簡單數據段,也就是說,它們的值直接存儲在變量訪問的位置。這是因為這些原始類型占據的空間是固定的,所以可將他們存儲在較小的內存區域 – 棧中。這樣存儲便于迅速查尋變量的值。
引用值:存儲在堆(heap)中的對象,也就是說,存儲在變量處的值是一個指針(point),指向存儲對象的內存地址。這是因為:引用值的大小會改變,所以不能把它放在棧中,否則會降低變量查尋的速度。相反,放在變量的棧空間中的值是該對象存儲在堆中的地址。地址的大小是固定的,所以把它存儲在棧中對變量性能無任何負面影響。
基本類型是將原始值保存在棧中。引用類型是將數據保存在堆中,然后在棧中建立堆地址的引用。在javascript中是不允許直接訪問保存在堆內存中的對象的,所以在訪問一個對象時,首先得到的是這個對象在堆內存中的地址,然后再按照這個地址去獲得這個對象中的值,這就是傳說中的按引用訪問。而原始類型的值則是可以直接訪問到的。不同的內存分配機制也帶來了不同的訪問機制。
而基本類型和引用類型在變量復制的時候也存在區別:
原始值:在將一個保存著原始值的變量復制給另一個變量時,會將原始值的副本賦值給新變量,此后這兩個變量是完全獨立的,他們只是擁有相同的 value 而已。
引用值:在將一個保存著對象內存地址的變量復制給另一個變量時,會把這個內存地址賦值給新變量,也就是說這兩個變量都指向了堆內存中的同一個對象,他們中任何一個作出的改變都會反映在另一個身上。復制是不會產生新的堆內存消耗。
所以我們總結 javascript 中所有函數參數都是按值傳遞,都是把形參復制給實參,只是基本數據類型復制的是原始值,而引用類型復制的是堆內存的地址。
引用類型的這種求值策略就是 按共享傳遞(call by sharing).
理論知識梳理完了,下面舉幾個例子分析一下具體的情形.
基本數據類型傳值let a = 1 function foo(x) { console.log(x) } foo(a) console.log(a) // 2 // 1
變量 a 的值直接復制到了foo函數的實參x上,此時變量x是變量 a 的一個副本。它們獨立的在各自的上下文棧中保存了值‘1’,且相互之間互不影響,我們對 a、x的讀寫操作,操作的是他們各自的值。
示例圖中,在全局上下文和foo的上下文中各自保存了值1.
引用類型傳值let a = { abc: 1 } function foo(x) { x.abc = 2 console.log(x.abc) } foo(a) console.log(a.abc) // 2 // 2
根據編碼經驗我們會很錯誤的得出,上面的栗子是按引用傳遞。對象a的引用被傳遞到函數foo內部, 函數內部變量x指向全局變量a,從而實現了引用的傳遞,所以變量x和變量a讀寫的是同一個對象。我們用一張圖來揭示:
但其實是按共享傳遞。按引用傳遞是我們對 javascript 求值策略的誤解,如果是按引用傳遞那下面這個例子就懵比了:
let a = { abc: 1 } function foo(x) { console.log(x) // {abc: 1} x = 2 console.log(x) // 2 } foo(a) console.log(a.abc) // 1
foo 函數執行時第一個打印輸出 ‘{abc: 1}’, 第二個打印輸出 ‘2’, 調用 foo 函數后的console.log(a.abc)一句打印輸出‘1’。
按引用傳遞的話。表達式console.log(a.abc)是在 foo 函數執行后執行的,此時的a應該已經被賦值為 2, a.abc是不存在的應該打印輸出 ‘undefind’。 但是卻打印輸出了 1,說明在 foo 函數的內部執行x = 2是并沒有修改外層對象 a 的值。
為什么會出現a、x在指向同一個對象后,對x賦值又沒有改變原對象的值呢?
因為這里對象傳遞給實參是按共享傳遞(call by sharing)的,根據引用類型變量復制的特點(上面描述過):
foo 函數執行時, 形參 x 的值是傳進去的對象 a 的內存地址引用,即在變量對象創建階段x保存的是一個對象的堆內存地址。此時 a、x 都指向同一對象。 接著在函數的執行階段,代碼的第二行將原始數據類型 2 賦值給 x,導致 x 不再保存原先的堆內存地址轉而保存一個原始值,再次訪問 x 的時候是訪問對 x 最后一次賦值的原始值。
所以對 x 的賦值會改變上下文棧中標識符 x 保存的具體值
此時如果使用的是按引用傳遞 ,則變量 a 所指向的對象因該也被賦值為 2。但其實對 x 賦值為一個基本數據類型并沒有使原對象為一個字面量值,這就說明引用類型并不是按引用傳值,不是遵從按引用規則來處理值的寫入。
需要區分給對象屬性賦值與直接給對象賦值的區別:
let a = { abc: 1 } function foo(x) { x.abc = 99 console.log(x) // {abc: 99} x = 2 console.log(x) // 2 } foo(a) console.log(a) // {abc: 99}
在 foo 函數內部修改對象 x 的屬性,會導致 x、a 指向的對象被修改,因為它們指向同一個堆地址。
參考:
《javascript高級程序設計》
javascript傳遞參
JS是按值傳遞還是按引用傳遞?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107840.html
摘要:中函數是一等公民。小明小明調用函數時,傳遞給函數的值被稱為函數的實參值傳遞,對應位置的函數參數名叫作形參。所以不推薦使用構造函數創建函數因為它需要的函數體作為字符串可能會阻止一些引擎優化也會引起瀏覽器資源回收等問題。 函數 之前幾節中圍繞著函數梳理了 this、原型鏈、作用域鏈、閉包等內容,這一節梳理一下函數本身的一些特點。 javascript 中函數是一等公民。 并且函數也是對象,...
閱讀 955·2021-11-17 09:33
閱讀 415·2019-08-30 11:16
閱讀 2468·2019-08-29 16:05
閱讀 3351·2019-08-29 15:28
閱讀 1393·2019-08-29 11:29
閱讀 1947·2019-08-26 13:51
閱讀 3385·2019-08-26 11:55
閱讀 1203·2019-08-26 11:31