js_關於為什麼'函式的引數都是按值傳遞'的理解
阿新 • • 發佈:2020-07-01
在向函式傳遞引數的過程中,引數的傳遞實際上是一種複製變數的操作.變數因為分為基本型別值和引用型別值,故在傳遞引數這一過程中有不同的表現.但終究它們都是按值傳遞的.
基本型別值與引用型別值的複製
- 棧記憶體(stack)用於儲存基本型別資料和引用型別資料的指標.它的讀取方式是先進後出(LIFO last-in-first-out)
- 堆記憶體(heap)用於儲存引用資料型別,比如object,array.heap沒有結構,資料任意存放.
- 基本型別值在複製過程中,被賦值變數獲取的是來源的副本.
- 引用型別值在複製過程中,複製的是引用型別值在棧記憶體中的指標的副本,堆記憶體的資料沒有任何變化.
var a = 1; var b = a; b = 2; console.log(`a:${a}`);//a:1 console.log(`b:${b}`);//b:2
如上程式碼所示,在將a賦值給b的過程裡,實際上是將a的副本賦值給b,在賦值完成後無論對基本型別值b做任何修改都不會干擾到a的值.
function setName(obj) { obj.name = 'Nicholas'; obj = new Object(); obj.name = 'Greg'; } var person = new Object(); setName(person); console.log(person.name); //'Nicholas'
如上程式碼所示,setName函式接收一個物件的指標的副本作為引數.將person物件的指標的副本作為引數傳遞到setName函式中.(賦值給arguments物件的obj元素)
執行程式碼 obj.name='Nicholas' 時,obj作為arguments物件中的元素,其值是指向堆記憶體中的物件實體的指標.因此對其新增元素將會根據指標指向直接修改堆記憶體中的物件.(注意obj只是儲存的person物件的指標的副本,全域性變數perosn依然儲存著指標)
執行程式碼 obj=new Object() 時,前面說過obj是arguments物件的元素,此時在區域性作用域中新建了一個物件實體,並將它的指標賦值給obj元素,此時obj元素與堆記憶體中的person物件徹底脫鉤,不再有任何聯絡.
執行程式碼 obj.name='Greg' 時,因為obj已經與原本傳入的person物件的指標副本無任何關係,現在儲存的是區域性作用域下的一個新物件的指標,為其新增name屬性,直接修改的是堆記憶體中的新物件.
執行程式碼完畢,銷燬函式的區域性作用域以及該作用域下的變數物件,arguments物件中的obj元素儲存的是堆記憶體中一個新物件的引用.obj元素隨著區域性作用域的銷燬而銷燬.失去了引用的引用資料也同樣在堆記憶體中被銷燬.
總結
- 簡單資料型別值就像是小紙片,上面寫好了資料並交由一個人(變數)保管.當其它人(變數)需要這個資料時,他需要複製這個資料,也就是用另一張小紙片抄一份.雖然資料相同,但是小紙片彼此之間並無關聯.其它人(變數)的小紙片上的資料可以被隨時被擦掉更改.而且小紙片的儲存方式是隨身攜帶.(棧記憶體儲存)
- 引用資料型別就像是一所房子的鑰匙.房子本身是建立在土地上的(堆記憶體儲存),一個人(變數)是無法隨身攜帶房子的,但是他可以隨身攜帶鑰匙(棧記憶體儲存).在這所房子建立時它就會有一個主人,這個主人儲存有鑰匙(建立物件的賦值操作),當執行引用型別資料的複製時,相當於持有房子的主人(變數)新配了一把鑰匙給另一個人(變數),這樣兩個人都可以對房子內的東西進行新增修改刪除.但是房子終究只有一個房子,只是多了一個人可以對它進行操作而已.
- 函式的傳參過程就是資料的複製過程,操作的都是棧記憶體中的資料,堆記憶體中儲存的資料並沒有被操作,也因此說"函式的引數都是按值傳遞".