1. 程式人生 > >[轉] javascript中的變量和垃圾回收

[轉] javascript中的變量和垃圾回收

tor 們的 問題 只有一個 次數 ie9 內存 通過 保持

[From] http://www.imooc.com/article/4585

基本類型和引用類型

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

基本類型有5種:Undefined,Null,Boolean,Number,String,基本類型是按值訪問的,因為可以操作保存在變量中的實際的值。

引用類型的值是保存在內存中的對象。與其他語言不同,JavaScript 不允許直接訪問內存中的位置,也就是說不能直接操作對象的內存空間。在操作對象時,實際上是在操作對象的引用(也稱為句柄)而不是實際的對象.

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

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

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時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後立即被銷毀。

可以把 ECMAScript 函數的參數想象成局部變量。

類型檢測

typeof運算符在檢測基本類型的時候非常有用,能夠識別出:string,number,boolean,undefined,但是對null和對象進行此運算得到的都是‘object‘.此操作符對於檢測對象來說沒有用(因為我們通常是想知道某值是不是對象,我們想知道它是什麽的實例).

對於檢測對象,我們有instanceof運算符:

result = variable instanceof constructor

如果對基本數據類型進行instanceof運算,得到的結果將是false,因為基本類型不是對象.

垃圾收集

垃圾收集機制其實非常簡單,周期性地找出不再繼續使用的變量,釋放其內存.標識無用變量的策略有以下的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 對象。這樣,就避免了兩種垃圾收集算法並存導致的問題,也消除了常見的內存泄漏現象。



[轉] javascript中的變量和垃圾回收