變量和內存管理
爲了合理的管理內存,操作系統一般會把內存劃分區域來使用,如代碼區、數據區等。被編譯成機器的碼的程序在執行時會被複制到內存的代碼區,程序中的變量和常量會被存放到數據區中。數據區,一般又分成:堆區、棧區、全局區……。各大語言的編譯器的內存模型一般會有一定的差別,不過基本都有堆和棧之分。
對於JavaScript
來說,爲了更好的理解,也需要去討論討論堆和棧。
棧區一般由系統自動分配存儲空間,用來存放局部變量、函數形參等一些固定大小的數據,保存在棧區的數據我們一般可以直接操作。因此,JavaScript
的基礎數據類型:Number
String
null
undefined
Boolean
,都是存放在棧區的,是按值訪問的。
在程序執行過程中申請的內存空間屬於堆區,一般存放一些較大且大小不固定的數據,堆區的數據一般不能直接被訪問。堆區空間一般需要程序員手動釋放,若程序員不釋放,程序結束時可能由系統回收 。因此,JavaScript
的複雜數據類型(Object
、Array
、Function
等)一般放到堆區。由於堆區的數據不能直接訪問,變量的值一般存放的並不是這些數據本身,而是該數據在內存中的地址,因此這些複雜的數據類型,也被成爲引用數據類型
。我們訪問一個堆區中的數據步驟一般是這樣的:首先從棧中獲取了該數據的地址引用,然後再從堆內存中取得我們需要的數據。
var a = 20;
var b= 'abc';
var c= true;
var d = {m: 20};
變量的複製
基礎數據類型的變量複製
var a = 20;
// 對於存在棧區的變量,a賦值給b變量,其實是將a變量的值複製一份存到內存中,然後將b變量指向新複製的數據
// 因此a與b其實已經是完全獨立的兩個變量,只是值一樣而已。
var b = a;
console.log(a, b);
b = 30;
console.log(a, b);
複雜數據類型的變量複製
JavaScript
是不允許直接訪問堆內存中的數據,所以如果我們要訪問複雜數據類型的時候,採用的是按引用訪問
,其實就是在變量對象
中存放了一個指向對象的句柄,可以理解爲一個地址,要訪問堆內存中的對象,就要通過這個引用句柄來訪問。
var m = { a: 10, b: 20 };
// 複雜數據類型的賦值,其實是將變量m中存的值(堆中的地址),複製一份出來放到內存中,然後將n指向新複製的值
// 因此,其實m和n最終指向的還是同一個堆中的同一個對象
var n = m;
console.log(m, n);
n.a = 15;
console.log(m, n);
內存泄漏
內存泄露可以定義爲:應用程序或者變量不再需要佔用內存的時候,由於某些原因,該內存並沒有被回收,我們就稱之爲內存泄漏。
內存垃圾回收
垃圾:不再需要的變量或引用即爲垃圾
JavaScript
垃圾回收機制常用方法:引用計數
、標記清除
,同時垃圾收集器是週期性運行的。
引用計數
在JavaScript
中,引用一般是針對複雜數據類型來說的。語言引擎有一張“引用表”,保存了內存裏面所有的資源的引用次數,當引用次數爲0的時候,垃圾回收機制會自動的回收其內存。但是有時候我們會遇到一個值不再需要了,引用數卻不爲0,這種垃圾回收機制是無法釋放這塊內存,這就會導致內存泄漏
,比如循環引用的時候。
// 單引用
var a = {name: 'ss'};
// 抹去了a對 {name: 'ss'}的引用
// {name: 'ss'} 的被引用次數就變成0了,所以它就成垃圾了
a = null;
// 循環引用
var a = {name: '666'};
var b = {name: '777'};
a.brother = b;
b.brother = a;
// 即使a和b分別對對象的引用斷掉,其實那兩個對象引用次數依然不爲0
// 但實際上我們已經不需要這兩個對象了
a = null;
b = null;
通過上面的循環引用問題,我們會看到,使用引用計數法進行垃圾回收不是很保險,可能會出現很嚴重的內存泄漏問題。因此,這種垃圾回收機制用的較少,已經很少有瀏覽器使用這種處理方式了。
標記清除
這是JavaScript
中最常用的垃圾回收方式。從全局作用域開始,一層一層往下標記,當變量進入執行環境時,就標記這個變量爲“進入環境”。當變量離開環境時,則將其標記爲“離開環境”。所有標記爲“進入環境”的變量不被清除,標記爲“離開環境”的變量會被清除。
使用這種方式,我們可以看到,對於全局變量來說,只要程序還在執行,那麼就不會被刪除,因此我們應該儘量少用全局變量,如果使用完以後,可以手動的將其值改爲null
或者使用delete
刪除。