編寫高性能JavaScript (讀書筆記)

轉載自http://kb.cnblogs.com/page/501177/  作者: Addy Osmani

讀書筆記:

JS引擎:Google:V8引擎Firefox:SpiderMonkeyOpera:CarakanIE:Chakra

作者以V8爲例講解:

一.核心構成部分

二.垃圾回收GC:Garbage Collecation

更喜歡叫做資源回收。

Q1:在JavaScript中,是不可能強制進行垃圾回收的。JS中無法強制釋放資源?

  垃圾回收好處是可以大幅簡化程序的內存管理代碼,降低程序員負擔,減少因長時間運轉帶來的內存泄露問題。

  但使用垃圾回收意味着程序員將無法掌控內存,ECMAscript沒有暴露任何垃圾回收器的接口,所以我們無法強迫進行垃圾回收和內存管理。但是可以通過比較委婉的方式使得GC認爲該變量沒用,從而回收。

管理內存
使具備垃圾收集機制的語言編寫程序,開發人員一般不必操心內存管理的問題。但是,javascript在進行內出你管理及垃圾收集時面臨的問題還是有點與衆不同。其中最重要的一個問題,就是分配給web瀏覽器的可使用內存數量通常要比分配給桌面應用程序的少。這樣做的目的出要是處於安全方面的考慮,目的是防止運行javascript的網頁耗盡全部系統內存而導致系統崩潰。內存限制問題不僅會影響給變量分配內存,同時還會影響調用棧以及在一個線程中能夠同時執行語句數量。
因此,確保佔用最少內存可以讓頁面獲得更好的性能,最好通過將其值設置爲null來釋放其引用——這個做飯叫做解除引用(dereferencing)。這一做法是用於大多數全局變量和全局對象的屬性。局部變量會在他們執行環境時自動被解除引用,如下面這個例子所示:
function createPerson (name) {
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
};
var gllbalPerson = createPerson("Nicholas");
// 手工解除globalPerson的引用
globalPerson = null;
在這個例子中,變量globalPerson取得了createPerson()函數返回的值。在createPerson()函數內部,我們創建了一個對象並將其賦給了局部變量localPerson,然後又爲該對象添加了一個名爲name的屬性。最後,當調用這個函數時,localPerson以函數的形式返回並賦給全局變量globalPerson。由於localPerson在createPerson()函數執行完畢後就離開了其執行環境,因此無需我們顯示的去爲他解除引用。但是對於全局變量globalPerson而言,則需要我們在不使用它的時候手工爲它解除引用,這也正是上面例子中最後一行代碼的目的。
不過,解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正作用是讓值脫離執行環境,一邊垃圾收集器下次運行時將其回收。

    Q2:JS中如何進行垃圾回收?

轉載自: http://www.phpchina.com/thread-221214-1-1.html

參考很多大神的講解,1.垃圾回收算法:

 A.引用計數(IE6/IE7/IE8)

     refernce counting,跟蹤記錄每個值被引用的次數。當聲明一個變量並將引用類型的值賦給該變量時,則這個值的引用次數就是1.如果同一個值又被賦給另一個變量,則該值的引用次數再加1.如果包含對這個值引用的變量又取得另外的值,則這個值的引用次數減1.當這個值的引用次數變成0時,則說明沒有辦法訪問這個值了,因此就可以將其佔用的內存空間回收回來。

循環引用問題:對象A中包含着對B的引用,B中又包含着對A的引用,使得這2個參數即使離開其作用域,其引用計數不會是0,不會被回收。
function () {
    var objectA = new Object();
      var objectB = new Object();   
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}
IE中有一部分對象並不是原生javascript對象。例如,其中BOM和DOM中的對象就是使用C++以COM (Component Object Model,組件對象模型)對象的形式實現的,
而COM對象的垃圾收集機制採用的就是引用計數策略。因此,即使IE的javascript引擎是使用標記清除策略來實現的,但javascript訪問的COM對象依然是基於引用計數策略的。
換句話說,只要IE中設計COM對象,就會存在循環引用的問題。下面這個簡單的例子,展示了使用COM對象導致的循環引用問題:
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.somObject = myObject;
這裏例子在一個DOM元素(element)與一個原生的javascript對象(myObject)之間創建了循環引用。其中,變量myObject有一個名爲element的屬性指向element對象;
而變量element也有一個屬性名叫someObject回指myObject。由於存在這個循環引用,即使將例子中的DOM從頁面中移除,它也永遠不會被回收。
爲了避免類似這樣的循環引用問題,最好是不使用他們的時候手工斷開原生javascript對象與DOM元素之間的連接。例如,可以使用下面的代碼消除前面例子創建的循環引用:
myObject.element = null;
element.somObject = null;
將變量設置爲null,意味着切斷變量與它此前引用的值之間的連接。但垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的內存。
   B.標記清除(現代瀏覽器)
mark-and-sweep,當變量進入環境中時,就將這個變量標記爲‘進入環境’,邏輯上講,不能釋放進入環境中的變量所佔用的內存,當變量離開環境時,將其標記爲‘離開環境’。
可以使用任何方式標記標量,翻轉某個特殊位;使用變量列表記錄;。
GC在運行時會給內存中所有的標量加上標記,然後,去掉環境中的變量以及被環境中變量引用的變量的標記。最後銷燬被標記的值並回收其所佔用的空間。感覺像是清理文件夾:先全選所有文件,再剔除留下要用的文件,然後delete...
2.回收也會消耗一部分性能,所以瀏覽器在瀏覽器佔用內存到一定數值的時候纔會執行垃圾回收(也有人說是固定時間間隔)。可以通過強制調用方法執行。
性能問題
垃圾收集器都是週期性運行的,而且如果爲變量分配的內存數量很客觀,那麼回收工作量也是相當大的。在這種情況下,確定垃圾收集的時間間隔是一個非常重要的問題。
說到垃圾收集器多長時間運行一次,不禁讓人聯想到IE因此聲名狼藉的性能問題。IE的垃圾收集器是根據內存分配量運行的,具體一點說就是256個變量、4096個對象(或數組)字面量和數組元素(slot)或
者64KB的字符串。達到上述任何一個臨界值,垃圾收集器就會運行。這種實現的問題在於,如果一個腳本中包含那麼多 變量,那麼該腳本很可能會在其生命中起一支保持那麼多的變量。而這樣一來,
垃圾收集器就可能不得不頻繁的運行。結果,由此引發的嚴重性能問題初始IE7重寫了其垃圾收集例程。
隨着IE7的發佈,其javascript引擎的垃圾收集例程改變了工作方式:觸發垃圾收集的變量分配、字面量和(或)數組元素的臨界值被調整爲動態修正。IE7中的各項臨界值在初始化時與IE6相等。
如果例程回收的內存分配量低於15%,則變量 、字面量和(或)數組元素的臨界值就會加倍。如果例程回收了85%的內存分配量,則將各種臨界重置會默認值。這一看似簡單的調整,極大地提升了
IE在運行包含大量javascript的頁面時的性能。
事實上,在有的瀏覽器中可以觸發垃圾收集過程,當我們不建議讀者這樣做。在IE中,調用window.CollectGarbage()方法會立即指向垃圾收集,在Opera7及更高版本中,調用widnow.opera.collect()也會啓動垃圾收集例程。


3.在一個函數中定義的對象,只要沒有引用函數外的其他對象,或者被函數外的其他引用,即使在該函數內相互引用,那麼在該函數執行完後
會被釋放回收。
Q3:內存泄露相關 
當一個DOM對象包含一個Js對象的引用(例如一個Event Handler), 而這個Js對象又持有對這個DOM對象的引用時,一個環狀引用就行成了。這本身並不是什麼錯誤或者Bug,因爲Js的回收機制能理解這種環狀的引用結構並且在沒有其他對象能關聯到環上的時候回收這個環上的所有對象內存。可不幸的是IE瀏覽器中的DOM結構並不受Js解釋機制管理,所以它並不能理解這種失去外界引用的環狀結構,導致環上任何對象都無法被訪問到,可是內存依舊佔據着,這也就是所謂的Js內存泄露了
由於IE對JScript對象和COM對象使用不同的垃圾收集例程,因此閉包在IE中會導致一些特殊的問題。具體來說,如果閉包的作用域鏈中保存着一個HTML元素,那麼就意味着該元素無法被銷燬。來看下面的例子:
function assignHandler () {
    var element = document.getElementById("someElement");
    element.onclick = function () {
            alert(element.id);
     };
}; 
以上代碼創建了一個作爲element元素時間處理程序的閉包,而這個閉包則有創建了一個循環引用。由於匿名函數保存了一個對assignHandler()的活動對象的引用,因此就會導致無法減少element的引用數。只要匿名函數存在,element的引用數至少也是1,因此它所佔用的內存就永遠不會被回收。不過,這個問題可以通過稍微改寫一下代碼來解決,如下所示:
function assignHandler () {
    var element = document.getElementById("someElement");
    var id = element.id;   
    element.onclick = function () {
            alert(id);
    }; 
    element = null;
};
在上面代碼中,通過把element.id的一個副本保存在一個變量中,並且在閉包中引用該變量消除了循環引用。但僅僅做到這一步,還是不能解決內存泄漏的問題。必須要記住:閉包會引用包含函數活動的整個活動對象,而其中包含着element。即使閉包不直接引用element,包含函數的活動對象中也仍然會保存一個引用。因此,有必要把element變量設置爲null。這樣就能夠解除對DOM對象的引用,順利地減少其引用數,確保正常回收其佔用的內存。
查詢了一些資料,貌似火狐等瀏覽器不會出現這種情況,好吧,萬惡的IE。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章