[轉] javascript中的變量和垃圾回收
[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
。在這個函數內部,obj
和p
引用的是同一個對象。換句話說,即使這個變量是按值傳遞的,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中的變量和垃圾回收