ES5中的值傳遞/引用傳遞--解釋

豫中呼保義發表於2017-09-14

先明確一個概念:棧和堆;不要管他們是什麼,你知道有這兩個東西就行;

var num1 = 8;

  實際上,宣告的num1這個變數它並沒有直接在棧中儲存一個值:8;

  當你var 宣告的時候,在堆中會被分配出一塊記憶體空間,這個空間有兩個資訊,一個是這個空間在堆中的地址,另一個是這個空間實際儲存的一個值:8;

  而棧中,給了兩個資訊,有一個識別符號叫做num1,它裡面被放進去了一個堆中的地址,這個地址,就指向堆中那個對應的儲存著值:8的那塊記憶體空間;

  

  

var num1 = 8; var num2 = num1;

  宣告瞭一個變數num1,值為8,值在記憶體空間裡;

  宣告瞭一個變數num2,值為num1的值,將num1的值在記憶體空間中複製一份,然後將複製的那一份記憶體空間的地址放入棧中的num2;

  如果這個時候我修改了num2的值,num1是不會改變的,為什麼?

  A有兩個兒子,一個叫B另一個叫C,A先給了B一百元錢,這個時候C看見了也想要,但是A就只有100元,於是A就用超能力,將B的一百元複製了一張給了C;

  這個時候,B和C都各自有一百元,但是C花了5塊錢買了一個棒棒糖,還剩95元,C花的是自己口袋裡的那一百元,不是B口袋裡的,所以B沒有買棒棒糖,B的口袋裡還是一百元,沒有變;

  

  【值傳遞】

  ————————————————————————————————————————————————————————————————————

  在《JavaScript高階程式設計》中,我讀到這麼一段話:在ECMAScript中,所有函式的引數都是【值傳遞】,不是【引用傳遞】;

  先解釋什麼是【值傳遞】:上面的num1和num2就是值傳遞;

  舉個例子: 

複製程式碼 var a = 10; function foo(num){ return num + 1; }; console.log(foo(a));//11 console.log(a);//10 複製程式碼   函式foo接受一個num引數,在函式體內將傳遞進來的這個引數加1並返回出去;

  這個時候,其實num就是一個變數,就是一個相對於foo這個函式的區域性變數,你可以這麼理解:var num = a;

  函式的引數是無法在函式外部獲得或使用的;

  都說了是值傳遞,所以他是將a的值複製了一份賦給了num,【num的地址指向和a的地址指向在堆中是不同的】,既然都不在同一個記憶體空間內,那我改變其中一個記憶體空間的值,是不可能影響到另一個記憶體空間的值的;

    

  實在是理解不了,看這段程式碼:

複製程式碼 var {log} = console;

    let nameArr = ['小紅','小李'];

    function add(arr){
        log(arguments);
    };

    add(nameArr);            

複製程式碼   啥意思呢?

  你肯定還記得arguments,本質上函式的引數列表是一個陣列,不是說值傳遞嗎?你把【值傳遞】倒過來看【傳遞值】;

  對,沒錯,傳遞的是值,不是地址;

  【引用傳遞】

  ————————————————————————————————————————————————————————————————————

  舉個例子,看程式碼你就明白了:

複製程式碼 var obj0 = { name:'小明' }; var obj1 = obj0; console.log(obj0.name);//小明 console.log(obj1.name);//小明 複製程式碼   物件obj0有個name屬性,值為字串:小明;

  我又建立了一個obj1物件,將obj0賦值給obj1;

  那麼,在堆中,是有兩個記憶體空間分別儲存著兩個物件嗎?

  不是的,其實是這樣的;

  這個賦值,其實只是把obj0的地址賦給了obj1,但實際上,在堆中,並沒有一個獨立的物件讓obj1去單獨指著,obj0和obj1的地址共同指向了堆中的同一個物件;

  這樣說的話,那麼我不管是在其中任何一個物件上做出修改,都會同時影響另外一個,另一個的相同屬性也會跟著改變;

  就是說,當複製儲存物件的某個變數的時候,操作的是物件的引用(地址),當修改新增物件的屬性/方法時,操作的是實際的物件本身;

  

  前方有坑,需謹慎:

  

複製程式碼 var fun = { name:'小紅' } function setName(obj){ obj.name = '老王' } setName(fun); console.log(fun.name);//老王 複製程式碼

  有個fun物件,這個物件有個name屬性,值為"小紅";

  執行setName這個函式,將fun這個物件傳遞了進去,這個時候是值傳遞還是引用傳遞?

  想都不用想,肯定是值傳遞,因為【JS中所有的函式的引數都是值傳遞】;

  那既然是值傳遞,我在函式內部修改了obj這個物件的name是不是不會影響到外面fun物件的name屬性;

  按道理來說,肯定是這樣的,但是偏偏就不是,在外面輸出了fun的name屬性,發現被更改了;

  實際上是這樣的:var obj = fun;

  即使函式的引數是隻能值傳遞,但是obj也會按引用訪問同一個物件;

  

  這裡非常的不好理解

  這樣想,純正的將一個物件賦值為另外一個物件,改變其中一個的某個屬性,另一個也會同時發生改變,因為他們是指向了堆中的同一個物件;

  上面程式碼中:函式的引數實際上是一個區域性變數,只對函式體內起作用;

  而程式碼中將fun的值,傳遞給區域性變數obj,那麼就是這樣:var obj = fun;

  實際上,你在setName這個函式中將arguments列印出來你是可以看到,obj的值是一個物件{name:'小紅'};這就更加的進一步證實,JS函式中的引數確實都是值傳遞;

  這就對上了,函式的引數都是值傳遞,沒錯,確實是fun的值;

  最後:在向引數傳遞引用型別的值的時候,會把這個值在記憶體中的地址也一併複製一份給區域性變數(就是引數);

  然後你就能明白這句話:obj還是會按照引用,就是地址,去訪問這個物件,訪問的地址相同,那肯定的,訪問的物件也就是同一個物件了!

  

  然後,然後現在你就已經理解了什麼是基本型別值、引用型別值,什麼是值傳遞,什麼是引用(地址)傳遞;

相關文章