堆和棧都是運行時內存中分配的一個數據區,因此也被稱為堆區和棧區,但二者存儲的數據類型和處理速度不同。堆(heap)用於復雜數據類型(引用類型)分配空間,例如數組對象、object對象;它是運行時動態分配內存的,因此存取速度較慢。棧(stack)中主要存放一些基本類型的變量和對象的引用,其優勢是存取速度比堆要快,並且棧內的數據可以共享,但缺點是存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。
1. 棧的使用規則
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。例如下面的代碼定義兩個變量,變量的值都是數字類型。
var a=3; var b=3;
JavaScript結石引起先處理 var a=3;,首先會在棧中創建一個變量為a引用,然后查找棧中是否有3這個值,如果沒有找到,就將3存放進來,然后將a指向3。接着處理 var b=3;,在創建為b的引用變量后,查找棧中是否有3這個值,因為此時棧中已經存在了3,便將b直接指向3。這樣,就出現了a與b同時指向3的情況。此時,如果再令a=4,那么JavaScript解釋引擎會重新搜查棧中是否有4這個值,如果有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
2. 堆的使用規則
下面通過Array來看一下堆的行為,例如存在下面的代碼:
var fruit_1="apple"; var fruit_2="orange"; var fruit_3="banana"; var oArray=[fruit_1,fruit_2,fruit_3]; var newArray=oArray;
當創建數組時,就會在堆內存創建一個數組對象,並且在棧內存中創建一個對數組的引用。變量fruit_1、fruit_2、fruit_3為基本數據類型,它們的值直接存放在棧中;newArray、oArray為復合數據類型(引用類型),他們的引用變量存放在棧中,指向於存放在堆中的實際對象。
注意,newArray的值等於變量引用oArray,所以它也是復合數據類型(引用類型)。此時,如果更改變量fruit_1、fruit_2、fruit_3的值,那么其實是更改棧中的值;如果更改newArray或oArray的值,那么其實是更該堆中的實際對象,因此,對兩個變量引用都會發生作用。例如,首先更改newArray的值,然后看oArray的值,代碼如下:
alert(oArray[1]);// 返回 orange newArray[1]="berry"; alert(oArray[1]);// 返回 berry
同樣,首現更改oArray的值,然后看newArray的值,代碼如下:
alert(newArray[1]);// 返回 orange oArray[1]='tomato'; alert(newArray[1]);// 返回 tomato
JavaScript堆不需要程序代碼來顯示地釋放,因為堆是由自動的垃圾回收來負責的,每種瀏覽器中的JavaScript解釋引擎有不同的自動回收方式,但一個最基本的原則是:如果棧中不存在對堆中某個對象的引用,那么就認為該對象已經不再需要,在垃圾回收時就會清除該對象占用的內存空間。因此,在不需要時應該將對對象的引用釋放掉,以利於垃圾回收,這樣就可以提高程序的性能。釋放對對象的引用最常用的方法就是為其賦值為null,例如下面代碼將newArray賦值為null:
newArray=null;
3. 易犯的錯誤
在堆和棧的使用問題上,最易犯的錯誤就是String的使用,例如下面的代碼:
var str=new String('abc'); var str='abc';
同樣是創建兩個字符串,第一種是用new關鍵字來新建String對象,對象會存放在堆中,每調用一次就會創建一個新的對象;而第二種是在棧中,棧中存放值‘abc’和對值的引用。推薦使用第二種方式創建多個'abc'字符串,這種寫法在內存中只存在一個值,有利於節省內存空間。同時它可以在一定程度上提高程序的運行速度,因為存儲在棧中,其值可以共享,並且由於棧訪問更快,所以對於性能的提高大有裨益。而第一種方式每次都在堆中創建一個新的String對象,而不管其字符串值是否相等及是否有必要創建新對象,從而加重了程序的負擔。並且堆的訪問速度慢,對程序性能的影響也大。另外,出於邏輯運算的考慮,當對兩個變量進行比較時,使用堆和棧存儲就會有差異。下面來看一下邏輯等於和邏輯全等運算,深入理解一下堆和棧:
(1) 例如下面的代碼,實際只比較棧中的值:
var str1='abc'; var str2='abc'; alert(str1==str2); // true alert(str1===str2); // true
不管是邏輯等於和邏輯全等運算都返回true,可以看出str1和str2指向同一個值。
(2)例如下面的代碼,實際只比較堆中的值:
var str1=new String('abc'); var str2=new String('abc'); alert(str1==str2); // false alert(str1===str2); // false
不管是邏輯等於還是邏輯全等都返回false,可以看出str1和str2指向的不是同一個對象。
(3)例如下面的代碼,比較堆和棧中的值:
var str1=new String('abc'); var str2='abc'; alert(str1==str2); // true alert(str1===str2); // false
在進行邏輯等於和邏輯全等運算時,會首先將變量轉成相同的數據類型,然后進行對比。變量str1和str2的數據類型雖然不同,但比較運算還是返回true。但邏輯全等運算與邏輯等於運算不同,它會對數據類型進行比較,看是否是引用的同一個數據。