值類型也稱爲原始數據或原始值(primitive value).這類值存儲在棧(stack)內存中, 基本類型的值不能夠修改。每當咱們定義一個變量,並賦給它一個基本類型的值時,能夠理解爲,咱們爲這個變量綁定了一個內存空間,這個內存空間存放的就是變量的值。所以。基本類型數據是存放在棧內存中的簡單數據段,數據大小肯定,內存空間大小能夠分配。
目前js中的基本類型一共有六種:null
,undefined
,boolean
,number
,,string
,symbol
。其中symbol是es6中新加的數據類型。這些類型在內存中分別佔用固定大小的空間,他們的值保存在棧空間,咱們經過按值來訪問的、
讓咱們看一個基本類型的值不能夠修改的示例es6
var a = 4; a = 3; //注意,這裏是覆蓋,不是修改 var num1 = 5; var num2 = num1; num1+=1; consol.log(num1);//6 console.log(num2);//5
從上面第二個例子能夠看到,從一個變量向另外一個變量複製基本類型的值,咱們會在變量對象上從新建立一個新值,而後把值複製到新變量分配的位置上,這兩個值是徹底獨立的,對着兩個變量進行操做是互不影響的。面試
這類值存儲在堆內存中,堆是內存中的動態區域,至關於自留空間,在程序運行期間會動態分配給代碼和堆棧。對中存儲的通常都是對象,而後在棧內存中存儲一個變量指針,計算機經過這個變量指針,找到堆中的數據塊並進行操做。這種訪問方式,咱們叫它按引用訪問。如圖所示。segmentfault
var fruit_1 = "apple"; var fruit_2 = "orange"; var fruit_3 = "banana"; var oArray = [fruit_1,fruit_2,fruit_3]; var newArray = oAarray;
當建立數組時,就會在堆內存中建立一個數組對象,而且在棧內存中建立一個對數組的引用。變量fruit_一、fruit_二、fruit_3爲基本數據類型,他們的值直接存放在棧中;newArray、oArray爲符合數據類型(引用類型),他們的引用變量存放在棧中,指向於存放在堆中的實際對象。
此時咱們改變oAarray中的值,對應的newArray也會改變,由於它們的存儲的指針指向同一個堆地址。數組
console.log(oArray[1]);// 返回 orange newArray[1]="berry"; console.log(oArray[1]);// 返回 berry
例如,下面代碼將newArray賦值爲null:app
newArray = null;
注意:接觸一個值的引用並不意味着自動回收改值所佔用的內存。解除引用的真正做用時讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。函數
與垃圾回收機制有關,爲了使程序運行時佔用的內存最小。
當一個方法執行時,每一個方法都會創建本身的內存棧,在這個方法內定義的變量會逐個放入這塊棧內存裏,隨着方法的執行結束,這個方法的內存棧也將天然銷燬了。所以,全部在方法中定義的變量都是放在棧內存中的;
當咱們在程序中建立一個對象時,這個對象將被保存到運行時數據區中,以便反覆理由(由於對象的建立成本一般比較大),這個運行時數據區就是堆內存。堆內存中的對象不會隨方法的結束而銷燬,即便方法結束後,這個對象還可能被另外一個引用變量所引用(方法的參數傳遞時很常見),則這個對象依然不會被銷燬,只有當一個對象沒有任何引用變量引用它時,系統的垃圾回收機制纔會在覈實的時候回收它。ui
var num = 10; function change(num){ num = num * 10; } change(num) console.log(num); // 2
能夠看到這裏的變量num在運行完函數之後,值並無發生改變。spa
var ab={ x:1, y:2 } function foo(obj){ obj.x = 2 } foo(ab) console.log(ab); //{x:2,y:2}
能夠看到,原來的ab對象,在函數foo調用以後,其中的對象屬性發生變化。由上面的兩個例子,咱們是否是能夠推斷在js中,對於基本類型的數據,在函數傳遞過程當中使用的時按值傳遞,而對於引用類型數據,在函數傳遞過程當中使用的時是按引用傳遞方式呢?讓咱們再看另一個例子。.net
var ab={ x:1, y:2 } function foo(obj){ obj = { x:2, y:3 } } foo(ab) console.log(ab); //{x:1,y:2}
這個示例,若是按照咱們剛剛說的結論,這裏的函數運行後,輸出的對象ab的值應該是{x:2,y:3}。可是真正的結果確實{x:1,y:2}。這就奇怪了,到底js中的函數參數的傳遞方式是按什麼樣的方式呢。我在網上找了不少資料,發現有個說法叫按共享傳遞。簡單來講,就是對於基本類型,是按值傳遞,對於對象而言,直接修改形參對實參沒有效果,而修改形參的屬性卻能夠同時修改實參的屬性。而個人理解是這樣的。看一張圖。
由咱們上面對引用類型與堆內存的介紹,咱們知道,當咱們定義一個ab對象時,系統會在堆中開出一個空間用來存儲改對象,對應的在棧內存中開出一個空間,用來存儲指向對象的地址。如上圖,根據按引用傳遞的方式,咱們知道,foo函數內部的obj。在函數編譯的時候,是指向ab所指向的對象的,兩者共用一個地址。當函數執行後,obj指向了新的對象地址,可是以前的ab所指向的對象屬性,依舊被ab所引用,沒有任何改變。因此,這裏輸出的ab的值依舊未變。
而對於函數內部,改變形參的屬性值這個狀況,咱們也能夠很容易的清楚,由於函數內部的obj和ab對象共同指向同一個對象空間,因此改變前者的對象屬性的值,天然會影響後者指針
var a = {n:1}; var b = a; a.x = a = {n:2}; console.log(a.x); console.log(b.x)
這道題考察了不少東西,js中的運算符的優先級,好比賦值運算,是從右到左的。js中的對象存儲的問題。可是這道題很容易根據賦值運算是從右到左的順序運行的來獲得錯誤的結果
a = {n:2} a.x = a
而後獲得了錯誤的答案。a.x = {n:2}.實際上,要解出這道題,咱們至少要知道兩個,第一個就是運算符的優先級問題,點運算符的優先級高於賦值運算級。因此這裏的執行順序第一步確定是a.x,而後纔是從右到左的賦值運算;第二個,咱們要知道的是,js對象在內存中是如何存儲的。知道這兩點,那這道題就不難解決了。一樣的,咱們繼續看幾張圖。
。
經過該題的前兩行代碼的聲明a和b,結合上面所說的對象存儲的原理,能夠很容易看明白。a和b指向同一個對象空間。
a.x = a = {n:2}
這行代碼,首先會運行a.x。這樣便會在{n:1}對象所存儲的空間上添加一個x屬性名,而且等待賦值。即原來的{n:1}變成{n:1,x:undefined}。而後,按照賦值運算的順序,先將變量a的指向變爲{n:2},可是這裏要注意,{n:1,x:undefined}因爲被b引用,因此依舊存在在內存當中。而後執行 ax.x = a,這裏要注意,這裏的a已是{n:2}了。而a.x則是b指向的{n:1,x:undefined}中的x屬性,因此最終b指向的{n:1,x:undefined}變爲{n:1,x:{n:2}}。
因此最後的結果。看下圖。
console.log(a.x); //undefined console.log(b.x); //{n:2}
參考連接:
https://www.imooc.com/article...
https://blog.csdn.net/lxiang2...
https://blog.csdn.net/xdd1991...
https://segmentfault.com/a/11...
https://blog.csdn.net/u012860...