變量的值
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();
```

每次`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

在這里,將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'

在這里,
obj1 在棧區(qū)存儲(chǔ)的指針指向 Object,然后將 obj1 復(fù)制給變量 obj2,會(huì)使得 obj2 也同時(shí)指向 Object。Object 才是真正的對(duì)象,而 obj1 和 obj2 都是對(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á)的意思,你說是不?
