JavaScript中記憶體使用規則--堆和棧

折翅2013發表於2018-12-13

堆和棧都是執行時記憶體中分配的一個資料區,因此也被稱為堆區和棧區,但二者儲存的資料型別和處理速度不同。堆(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。但邏輯全等運算與邏輯等於運算不同,它會對資料型別進行比較,看是否是引用的同一個資料。

相關文章