淺談變量中的數(shù)據(jù)類型

變量的值

ECMAScript 變量可能包含兩種不同數(shù)據(jù)類型的值:

  • 基本類型值:string , number , boolean , undefined , null
  • 引用類型值:object

堆和棧

在理解它們之間的差異之前,我們先需要來了解一下關(guān)于堆和棧的概念

  • 基本類型的變量的存儲(chǔ)只利用棧區(qū)
    可以看看這個(gè)例子
    var name = 'Sumi';
    var age = '22';
    var city = 'ShangHai';
    
    可以看一下下圖方便理解
    基本類型的變量存放在棧區(qū)

    棧區(qū)包括了變量的標(biāo)識(shí)符
  • 引用類型的變量的存儲(chǔ)要同時(shí)利用到棧區(qū)堆區(qū)

這里是一個(gè)簡(jiǎn)單例子

var person1 = new Object();
var person2 = new Object();
var person3 = new Object();
 ```
![對(duì)象賦值的變量同時(shí)要用到堆區(qū)和棧區(qū)](http://upload-images.jianshu.io/upload_images/2964515-2a1dc8421490bad7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
每次`new`一個(gè)對(duì)象的時(shí)候,都會(huì)在堆區(qū)中新建一個(gè)對(duì)象,所以這里是三個(gè)不同的對(duì)象,分別賦值給了person1、person2、person3這三個(gè)變量

 -   **棧區(qū)**保存的是對(duì)對(duì)象的**引用**(變量的標(biāo)識(shí)符(名字)、指針(即該對(duì)象在堆內(nèi)存的地址))
 -   **堆區(qū)**保存的是對(duì)象的**值**(屬性、方法) 

####分析和總結(jié)

>   JavaScript 不允許直接訪問內(nèi)存中的位置,也就是說不能直接操作對(duì)象的內(nèi)存空間。在操作對(duì)象時(shí),實(shí)際上是在操作對(duì)象的引用而不是實(shí)際的對(duì)象(這句話不太嚴(yán)謹(jǐn),當(dāng)復(fù)制保存著對(duì)象的某個(gè)變量時(shí),操作的是對(duì)象的引用。但在為對(duì)象添加屬性時(shí),操作的是實(shí)際的對(duì)象)

先貼上高程里面對(duì)于對(duì)象的總結(jié),這里的指針可能對(duì)于很多人來說不太好理解,我們采用一種較為形象的方式來便于理解
-   對(duì)于基本數(shù)據(jù)變量:可以理解為一個(gè)`p`標(biāo)簽,標(biāo)簽里面是它的內(nèi)容,即變量的值,它的值是顯而易見的顯示在網(wǎng)頁上面的,你可以直接在這個(gè)網(wǎng)頁看到`p`的內(nèi)容
-   對(duì)于引用數(shù)據(jù)變量:可以理解為一個(gè)`a`標(biāo)簽,`a`標(biāo)簽有一個(gè)`href`屬性包含了一個(gè)鏈接,只有當(dāng)你通過這個(gè)鏈接訪問到另外一個(gè)網(wǎng)頁的時(shí)候,才能看到另外網(wǎng)頁的真正內(nèi)容,在當(dāng)前頁是無法查看到的。這里的棧區(qū)保存的指針可以理解為`a`標(biāo)簽的`href`的屬性,另外一個(gè)網(wǎng)頁的內(nèi)容可以理解為堆區(qū)的對(duì)象
 >   對(duì)于`a`標(biāo)簽來說,只有通過`href`里面的鏈接訪問進(jìn)另外一個(gè)網(wǎng)頁,才能看到另外一個(gè)網(wǎng)頁真正的內(nèi)容
 >   對(duì)于對(duì)象復(fù)制的變量來說,只有通過棧區(qū)的指針訪問進(jìn)堆區(qū)的對(duì)象,才能獲取到對(duì)象的值


##基本類型變量特性
1.   基本類型的值是**不可變**的,也不能給基本類型添加屬性和方法
 -   任何方法都無法改變一個(gè)基本類型的值,比如這里有一個(gè)字符串:
     ```javascript
     var name = 'Sumi';
     name.toUpperCase();
     console.log(name);      //'Sumi'
     ```
     最后輸出時(shí)會(huì)發(fā)現(xiàn)原始的name并未發(fā)生改變,這里調(diào)用了toUpperCase()方法后,返回的是一個(gè)新的字符串

 -   **不能**給基本類型添加屬性和方法,再次說明基本類型不可變
     ```javascript
     var person = 'Sumi';
     person.age = 22;
     console.log(person.age);        //undefined
     person.method = function(){};
     console.log(person.method);     //undefined
     ```
2.   基本類型的變量的存儲(chǔ)只利用**棧區(qū)**

3.   基本類型的比較是**值**和**數(shù)據(jù)類型**比較,只有在值和數(shù)據(jù)類型都相等的時(shí)候它們才全等(`===`)
 不過有個(gè)例子很容易讓人混淆
 ```javascript
 var a = 1;
 var b = true;
 console.log( a == b );          //true
 ```
 這里涉及到了數(shù)據(jù)類型轉(zhuǎn)換和`==`運(yùn)算符的知識(shí),它們等于不代表全等

##引用類型變量特性
1.   引用類型的值是**可變**的
 我們可為為引用類型動(dòng)態(tài)地添加屬性和方法,也可以動(dòng)態(tài)地刪除其屬性和方法
 ```javascript
 var person = new Object();
 person.name = 'Sumi';           //給變量動(dòng)態(tài)添加屬性name
 person.sayName = function(){    //給變量動(dòng)態(tài)添加方法sayName
     console.log(person.name);
 };
 person.sayName();               //'Sumi'

 delete person.name;             //動(dòng)態(tài)刪除變量的屬性name
 console.log(person.name);       //undefined
 ```

2.   引用類型的變量的存儲(chǔ)要同時(shí)利用到**棧區(qū)**和**堆區(qū)**

3.   引用類型的比較是**棧區(qū)存儲(chǔ)的指針**和**堆區(qū)存儲(chǔ)的對(duì)象的值**的比較,只有在這兩者相等時(shí),它們才全等(`===`)
```javascript
var person1 = new Object();
var person2 = new Object();
console.log( person1 == person2 );    //false

就像上文中說到的,每次new一個(gè)對(duì)象時(shí),都會(huì)在堆區(qū)新建一個(gè)對(duì)象,那么person1和person2的指向是不同的,它們分別指向了兩個(gè)對(duì)象,所以結(jié)果為false

變量的復(fù)制原理以及訪問類型

其實(shí)復(fù)制原理可以算作是特性里面的內(nèi)容,由于這里需要設(shè)計(jì)到訪問類型,特意拿出來做一個(gè)比較明顯的對(duì)比

對(duì)于基本類型變量來說(按值訪問)

從一個(gè)變量向另一個(gè)變量復(fù)制基本類型的值,會(huì)在變量對(duì)象上創(chuàng)建一個(gè)新值,然后把該值復(fù)制到為新變量分配的位置上,兩個(gè)變量的值相互獨(dú)立,互不影響

來看一個(gè)例子:

var num1 = 5;
var num2 = num1;
num2 = 4;
console.log(num1);    //5
基本類型變量復(fù)制:前后棧區(qū)對(duì)比

在這里,將num1復(fù)制給了num2,會(huì)在num1上新建一個(gè)變量num2,當(dāng)我修改num2的時(shí),num1的是不會(huì)受到影響的

對(duì)于引用類型變量來說(按引用訪問)

當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí),同樣也會(huì)將存儲(chǔ)在變量對(duì)象中的值復(fù)制一份放到為新變量分配的空間中。不同的是,這個(gè)值的副本實(shí)際上是一個(gè)指針(堆內(nèi)存地址),而這個(gè)指針指向存儲(chǔ)在堆中的一個(gè)對(duì)象。復(fù)制操作結(jié)束后,兩個(gè)變量實(shí)際上將引用堆區(qū)的同一個(gè)對(duì)象,而對(duì)象的值都保存在這里,因此,改變其中一個(gè)變量的值,就會(huì)影響另一個(gè)變量的值

來看一個(gè)例子:

var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'Sumi';
alert(obj2.name);    //'Sumi'

引用類型變量復(fù)制

在這里,obj1棧區(qū)存儲(chǔ)的指針指向 Object,然后將 obj1 復(fù)制給變量 obj2,會(huì)使得 obj2 也同時(shí)指向 ObjectObject 才是真正的對(duì)象,而 obj1obj2 都是對(duì)于 Object引用,它們?cè)谛薷闹担▽傩?、方法)的時(shí)候都會(huì)修改堆區(qū)Object 從而使得 obj1 的修改反應(yīng)到 ob2 身上

對(duì)于函數(shù)傳參(按值訪問)

函數(shù)傳參在也是屬于變量的復(fù)制

原理就是把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣。參數(shù)不管是基本類型還是引用類型,都屬于按值訪問

基本類型變量于上面說過的復(fù)制方式相同,這里著重說一下參數(shù)為引用類型的情況:
當(dāng)參數(shù)為引用類型時(shí),并不會(huì)將對(duì)象本身傳進(jìn)函數(shù)內(nèi),而是會(huì)在函數(shù)內(nèi)新建一個(gè)局部變量,并將對(duì)象的指針復(fù)制給函數(shù)內(nèi)的局部變量。此時(shí),局部變量和外部的變量都指向堆區(qū)的同一個(gè)對(duì)象,導(dǎo)致的結(jié)果就是內(nèi)部對(duì)于對(duì)象的修改會(huì)影響到外部變量,外部對(duì)于對(duì)象的修改也會(huì)反應(yīng)到內(nèi)部,在函數(shù)內(nèi)的局部變量被重新賦值之前,這種現(xiàn)象都會(huì)一直存在。如果函數(shù)內(nèi)的變量被重新賦值,那么外部變量和局部變量的指向?qū)⒉煌@種現(xiàn)象也會(huì)消失

注意:在《高級(jí)程序設(shè)計(jì)》里提出了一個(gè)例子來對(duì)按引用訪問和按值訪問進(jìn)行了區(qū)分,如果對(duì)例子的描述進(jìn)行分析會(huì)發(fā)現(xiàn),實(shí)際上會(huì)發(fā)現(xiàn)例子并不能證明按引用訪問和按值訪問的區(qū)別,筆者暫時(shí)也無法總結(jié)出關(guān)于按引用訪問和按值訪問的差別和特性,這里只推薦大家記住相關(guān)結(jié)論,如果進(jìn)行深究會(huì)很容易把自己繞進(jìn)去。

小結(jié)

關(guān)于概念的深摳和總結(jié)不太適合初學(xué)者,JS作為一門弱類型語言還是會(huì)存在很多概念不清楚的地方,咱的目標(biāo)是清楚原理(變量的值在堆、棧中的存儲(chǔ)方式等),知道結(jié)論(訪問方式是按值訪問還是按引用訪問)。這也是我這篇文章想表達(dá)的意思,你說是不?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容