JS變量和垃圾回收機制

基本類型和引用類型

js中的變量雖然不區分類型,可是實際上Ecmascript包含兩種類型,基本類型和引用類型.java

基本類型有5種:Undefined,Null,Boolean,Number,String,基本類型是按值訪問的,由於能夠操做保存在變量中的實際的值。算法

引用類型的值是保存在內存中的對象。與其餘語言不一樣,JavaScript 不容許直接訪問內存中的位置,也就是說不能直接操做對象的內存空間。在操做對象時,其實是在操做對象的引用(也稱爲句柄)而不是實際的對象.瀏覽器

注意:
字符串字面量是基本數據類型,這和其餘的語言(例如java)有所區別.

js中函數參數的傳遞只有一種:值傳遞.請看下面的例子:bash

function setName (obj) {
    obj.name = 'hello';
}

var p = new Object();
setName(p);
console.log(p.name); // hello複製代碼

以上代碼中建立一個對象,並將其保存在了變量p中。而後,這個變量被傳遞到setName()函數中以後就被複制給了obj。在這個函數內部,objp引用的是同一個對象。換句話說,即便這個變量是按值傳遞的,obj也會按引用來訪問同一個對象。因而,當在函數內部爲obj添加name屬性後,函數外部的p也將有所反映;由於p指向的對象在堆內存中只有一個,並且是全局對象。有不少開發人員錯誤地認爲:在局部做用域中修改的對象會在全局做用域中反映出來,就說明參數是按引用傳遞的。爲了證實對象是按值傳遞的,咱們再看一看下面這個通過修改的例子:函數

function setName (obj) {
    obj.name = 'hello';

    obj = new Object();
    obj.name = 'gray';
}

var p = new Object();
setName(p);
console.log(p.name); // hello複製代碼

這個例子與前一個例子的惟一區別,就是在setName()函數中添加了兩行代碼:一行代碼爲obj從新定義了一個對象,另外一行代碼爲該對象定義了一個帶有不一樣值的name屬性。在把p傳遞給setName()後,其name屬性被設置爲"hello"。而後,又將一個新對象賦給變量obj,同時將其name屬性設置爲"gray"。若是p是按引用傳遞的,那麼p就會自動被修改成指向其name屬性值爲"gray"的新對象。可是,當接下來再訪問person.name時,顯示的值仍然是"hello"。這說明:即便在函數內部修改了參數的值,但原始的引用仍然保持未變。實際上,當在函數內部重寫obj時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後當即被銷燬ui

能夠把 ECMAScript 函數的參數想象成局部變量。

類型檢測

typeof運算符在檢測基本類型的時候很是有用,可以識別出:string,number,boolean,undefined,可是對null和對象進行此運算獲得的都是'object'.此操做符對於檢測對象來講沒有用(由於咱們一般是想知道某值是否是對象,咱們想知道它是什麼的實例).spa

對於檢測對象,咱們有instanceof運算符:指針

result = variable instanceof constructor複製代碼

若是對基本數據類型進行instanceof運算,獲得的結果將是false,由於基本類型不是對象.code

垃圾收集

垃圾收集機制其實很是簡單,週期性地找出再也不繼續使用的變量,釋放其內存.標識無用變量的策略有如下的2種方式:對象

標記清除(mark-and-sweep)

絕大多數瀏覽器採用的垃圾收集機制.

當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲「離開環境」。

可使用任何方式來標記變量。好比,能夠經過翻轉某個特殊的位來記錄一個變量什麼時候進入環境,或者使用一個「進入環境的」變量列表及一個「離開環境的」變量列表來跟蹤哪一個變量發生了變化。說到底,如何標記變量其實並不重要,關鍵在於採起什麼策略.

垃圾收集器在運行的時候會給存儲在內存中的全部變量都加上標記(固然,可使用任何標記方式)。而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。最後,垃圾收集器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。

引用計數(reference counting)

跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。若是同一個值又被賦給另外一個變量,則該值的引用次數加1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數減 1。當這個值的引用次數變成 0 時,則說明沒有辦法再訪問這個值了,於是就能夠將其佔用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數爲零的值所佔用的內存。

這種方式存在一個嚴重的問題循環引用.循環引用指的是對象 A 中包含一個指向對象 B 的指針,而對象 B 中也包含一個指向對象 A 的引用。

function problem() {
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}複製代碼

在這個例子中, objectA 和 objectB 經過各自的屬性相互引用;也就是說,這兩個對象的引用次數都是2。在採用標記清除策略的實現中,因爲函數執行以後,這兩個對象都離開了做用域,所以這種相互引用不是個問題。但在採用引用計數策略的實現中,當函數執行完畢後,objectA 和 objectB 還將繼續存在,由於它們的引用次數永遠不會是0。假如這個函數被重複屢次調用,就會致使大量內存得不到回收。爲此,Netscape 在 Navigator 4.0中放棄了引用計數方式,轉而採用標記清除來實現其垃圾收集機制。但是,引用計數致使的麻煩並未就此終結。

IE 中有一部分對象並非原生JavaScript對象。例如,其BOM和DOM中的對象就是使用C++以COM(Component Object Model,組件對象模型)對象的形式實現的,而 COM 對象的垃圾收集機制採用的就是引用計數策略。所以,即便IE的JavaScript引擎是使用標記清除策略來實現的,但JavaScript訪問的COM對象依然是基於引用計數策略的。換句話說,只要在 IE 中涉及 COM 對象,就會存在循環引用的問題。爲了解決上述問題,IE9 把 BOM 和 DOM 對象都轉換成了真正的 JavaScript 對象。這樣,就避免了兩種垃圾收集算法並存致使的問題,也消除了常見的內存泄漏現象。

相關文章
相關標籤/搜索