1. 程式人生 > 實用技巧 >js_關於為什麼'函式的引數都是按值傳遞'的理解

js_關於為什麼'函式的引數都是按值傳遞'的理解

在向函式傳遞引數的過程中,引數的傳遞實際上是一種複製變數的操作.變數因為分為基本型別值和引用型別值,故在傳遞引數這一過程中有不同的表現.但終究它們都是按值傳遞的.

基本型別值與引用型別值的複製

  • 棧記憶體(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元素隨著區域性作用域的銷燬而銷燬.失去了引用的引用資料也同樣在堆記憶體中被銷燬.

總結

  • 簡單資料型別值就像是小紙片,上面寫好了資料並交由一個人(變數)保管.當其它人(變數)需要這個資料時,他需要複製這個資料,也就是用另一張小紙片抄一份.雖然資料相同,但是小紙片彼此之間並無關聯.其它人(變數)的小紙片上的資料可以被隨時被擦掉更改.而且小紙片的儲存方式是隨身攜帶.(棧記憶體儲存)
  • 引用資料型別就像是一所房子的鑰匙.房子本身是建立在土地上的(堆記憶體儲存),一個人(變數)是無法隨身攜帶房子的,但是他可以隨身攜帶鑰匙(棧記憶體儲存).在這所房子建立時它就會有一個主人,這個主人儲存有鑰匙(建立物件的賦值操作),當執行引用型別資料的複製時,相當於持有房子的主人(變數)新配了一把鑰匙給另一個人(變數),這樣兩個人都可以對房子內的東西進行新增修改刪除.但是房子終究只有一個房子,只是多了一個人可以對它進行操作而已.
  • 函式的傳參過程就是資料的複製過程,操作的都是棧記憶體中的資料,堆記憶體中儲存的資料並沒有被操作,也因此說"函式的引數都是按值傳遞".