1. 程式人生 > >像js函式中傳遞物件引數

像js函式中傳遞物件引數

               

知乎上的一個問題:http://www.zhihu.com/question/27114726

javascript傳遞引數如果是object的話,是按值傳遞還是按引用傳遞?

著作權歸作者所有。
商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
作者:蘇墨橘
連結:http://www.zhihu.com/question/27114726/answer/35481766
來源:知乎

之前第一次看到這部分的時候也有點雲裡霧裡,今天看到題主問這個問題又仔細地看了一遍,查閱了相關資料,現在算是比較清楚了。
結合自己的理解整理了一下。
P.S. 如果覺得知乎上的閱讀體驗不佳,建議直接訪問我的部落格看,字型、大小、行間距都是精心挑選的喲~ 戳這裡---->Js中值的訪問與引數傳遞的問題
因為要徹底說清楚這個問題就涉及一些更深入的知識點,所以篇幅較長,望耐心看完。
其實你只要弄清楚一點就可以了:儲存物件的變數,它裡面裝的值是這個物件在堆記憶體中的地址。
以下是詳細分解:

資料型別

在 javascript 中資料型別可以分為兩類:

  • 原始資料型別值
    primitive type,比如Undefined,Null,Boolean,Number,String。
  • 引用型別值,也就是物件型別 Object type,比如Object,Array,Function,Date等。
宣告變數時不同的記憶體分配
  • 原始值:儲存在棧(stack)中的簡單資料段,也就是說,它們的值直接儲存在變數訪問的位置。這是因為這些原始型別佔據的空間是固定的,所以可將他們儲存在較小的記憶體區域 – 棧中。這樣儲存便於迅速查尋變數的值。
  • 引用值:儲存在堆(heap)中的物件,也就是說,儲存在變數處的值是一個指標(point),指向儲存物件的記憶體地址。這是因為:引用值的大小會改變,所以不能把它放在棧中,否則會降低變數查尋的速度。相反,放在變數的棧空間中的值是該物件儲存在堆中的地址。地址的大小是固定的,所以把它儲存在棧中對變數效能無任何負面影響。<img src="https://pic4.zhimg.com/55261f168f9369f6dfa53dbcd01aa1ff_b.jpg" data-rawwidth="667" data-rawheight="419" class="origin_image zh-lightbox-thumb" width="667" data-original="https://pic4.zhimg.com/55261f168f9369f6dfa53dbcd01aa1ff_r.jpg">

不同的記憶體分配機制也帶來了不同的訪問機制

在javascript中是不允許直接訪問儲存在堆記憶體中的物件的,所以在訪問一個物件時,首先得到的是這個物件在堆記憶體中的地址,然後再按照這個地址去獲得這個物件中的值,這就是傳說中的按引用訪問。而原始型別的值則是可以直接訪問到的。

複製變數時的不同
  • 原始值:在將一個儲存著原始值的變數複製給另一個變數時,會將原始值的副本賦值給新變數,此後這兩個變數是完全獨立的,他們只是擁有相同的value而已。<img src="https://pic2.zhimg.com/6ff3fb467ec508d8dab223d25618d611_b.jpg" data-rawwidth="526" data-rawheight="336" class="origin_image zh-lightbox-thumb" width="526" data-original="https://pic2.zhimg.com/6ff3fb467ec508d8dab223d25618d611_r.jpg">
  • 引用值:在將一個儲存著物件記憶體地址的變數複製給另一個變數時,會把這個記憶體地址賦值給新變數,也就是說這兩個變數都指向了堆記憶體中的同一個物件,他們中任何一個作出的改變都會反映在另一個身上。(這裡要理解的一點就是,複製物件時並不會在堆記憶體中新生成一個一模一樣的物件,只是多了一個儲存指向這個物件指標的變數罷了)<img src="https://pic2.zhimg.com/0890f4f6faa8eeaa7f25cd1d769ceef5_b.jpg" data-rawwidth="793" data-rawheight="359" class="origin_image zh-lightbox-thumb" width="793" data-original="https://pic2.zhimg.com/0890f4f6faa8eeaa7f25cd1d769ceef5_r.jpg">
引數傳遞的不同

首先我們應該明確一點:ECMAScript中所有函式的引數都是按值來傳遞的。但是為什麼涉及到原始型別與引用型別的值時仍然有區別呢,還不就是因為記憶體分配時的差別。 (我對比了一下,這裡和複製變數時遵循的機制完全一樣的嘛,你可以簡單地理解為傳遞引數的時候,就是把實參複製給形參的過程)

  • 原始值:只是把變數裡的值傳遞給引數,之後引數和這個變數互不影響。
引用值:物件變數它裡面的值是這個物件在堆記憶體中的記憶體地址,這一點你要時刻銘記在心!因此它傳遞的值也就是這個記憶體地址,這也就是為什麼函式內部對這個引數的修改會體現在外部的原因了,因為它們都指向同一個物件呀。或許我這麼說了以後你對書上的例子還是有點不太理解,那麼請看圖吧:<img src="https://pic1.zhimg.com/33472bcc2789b7d071f119169b7d6e20_b.jpg" data-rawwidth="719" data-rawheight="419" class="origin_image zh-lightbox-thumb" width="719" data-original="https://pic1.zhimg.com/33472bcc2789b7d071f119169b7d6e20_r.jpg">
所以,如果是按引用傳遞的話,是把第二格中的內容(也就是變數本身)整個傳遞進去(就不會有第四格的存在了)。但事實是變數把它裡面的值傳遞(複製)給了引數,讓這個引數也指向原物件。因此如果在函式內部給這個引數賦值另一個物件時,這個引數就會更改它的值為新物件的記憶體地址指向新的物件,但此時原來的變數仍然指向原來的物件,這時候他們是相互獨立的;但如果這個引數是改變物件內部的屬性的話,這個改變會體現在外部,因為他們共同指向的這個物件被修改了呀!來看下面這個例子吧:(傳說中的call by sharing)
var obj1 = {  value:'111'}; var obj2 = {  value:'222'}; function changeStuff(obj){  obj.value = '333';  obj = obj2;  return obj.value;}  var foo = changeStuff(obj1); console.log(foo);// '222' 引數obj指向了新的物件obj2console.log(obj1.value);//'333'

code裡的註釋太小看不清,我移到這裡來:

/* obj1仍然指向原來的物件,之所以value改變了, *是因為changeStuff裡的第一條語句,這個時候obj是指向obj1的 . *再囉嗦一句,如果是按引用傳遞的話,這個時候obj1.value應該是等於'222'的*/

好了,以上就是關於這個問題的全部解釋了。

各位有興趣的話可以去了解一下call by value ,call by reference call by sharing 等函式傳遞的機制call by sharing

還有stackoverflow上對於函式傳遞的這個問題解釋得相當精闢,值得一看。(下面有連結)


參考:


有哪裡不明白或者覺得我表述不清楚、不正確的地方,歡迎提出一起討論~ (編輯於2014.12.19)

------------2015.4.4 更新 ----------
發現這位同學解釋得也很清晰,大家可以看看
JS是按值傳遞還是按引用傳遞? | BOSN.ME


           

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://blog.csdn.net/jiangjunshow