頻繁地對於DOM進行操作的很是損耗性能,但在富網頁應用中我們編寫腳本無可避免地要跟DOM打交道,到底怎么才能優化這個性能瓶頸呢,大致從以下三種情況去考慮:
- 訪問和修改DOM元素
- 修改DOM樣式,會造成頁面的重繪和重新排版
- 通過DOM事件處理程序來響應用戶
訪問和修改DOM元素
在瀏覽器中,DOM的實現和Javascript的實現通常是保持相互獨立的。下面了解一下主流瀏覽器的渲染引擎和JS引擎:
瀏覽器 | 渲染引擎(內核) | JS引擎 |
---|---|---|
IE | mshtml.dll(Trident) | JScript |
Chrome | WebCore(WebKit) | V8 |
FireFox | Gecko | Spider-Monkey/TraceMonkey |
Safari | WebCore(WebKit) | JavaScriptCore/SquirrelFish |
為什么訪問DOM對性能有影響?那是因為兩個獨立的部分通過他們各自的接口來連接就會帶來性能損耗。打個比喻,把DOM看成一個島嶼,把Javascript看成另外一個島嶼,兩者之間以一座要收費的橋連接,每次Javascript訪問DOM都需要過橋,交一次橋費。來回多了費用自然就高了。所以我們得想方設法減少過橋的次數。
訪問DOM元素的代價就是交一次“橋費”,修改DOM元素則會導致瀏覽器重新計算頁面的幾何變化。如果是循環修改DOM元素,其代價可想而知。如下代碼:
function innerHTMLLoop1(){ for(var count=0;count<10000;count++){ document.getElementById("test").innerHTML +="增加內容"; } }
在這段代碼中,每循環一次都要對DOM元素訪問兩次:一次是讀取innerHTML屬性的內容,另一次是把新的內容寫入它。所以一個優化的辦法就是使用一個局部變量存儲更新后的內容,在循環結束時再一次性寫入:
function innerHTMLLoop2(){
var content =""; for(var count=0;count<10000;count++){ content +="增加內容"; } document.getElementById("test").innerHTML += content ; }
很明顯,innerHTMLLoop2交的“橋費”明顯要少,因為它只訪問了兩次DOM元素,一次讀入,一次寫入。所以一般的法則是:盡量在自己的范圍內(Javascript島嶼)活動,別經常到外面鬼混去。(據說,在廣州,月薪不到5K的別交女朋友,你懂的!)。
更新頁面的兩種方法性能比較:innerHTML和DOM方法(如document.creatElment)。《高性能Javascript編程》的答案是:性能差別不大,innerHTML好一些,使用簡單嘛。另外還有一個更新頁面的方法是節點克隆--element.cloneNode()。
HTML集合的操作
HTML集合是用於存放DOM節點引用的類數組對象。可通過下列的方法或屬性得到這樣的集合:
- document.getElementsByName()
- document.getElementsByTagName()
- document.getElementsByClassName()
- document.images 返回對文檔中所有 Image 對象引用
- document.links 返回對文檔中所有 Area 和 Link 對象引用
- document.forms 返回對文檔中所有 Form 對象引用
- document.forms[0].elements 返回對文檔中第一個表單的所有元素
HTML集合會實時查詢文檔信息,也就是說當你要用到這個集合時,它會自動查詢文檔的最新信息。請看如下代碼:
1 var allDivs = document.getElementsByTagName("div"); 2 for(var i=0;i<allDivs.length;i++){ 3 document.body.appendChild(document.createElement("div")); 4 }
例如像上面的那段代碼其實是個死循環,因為每一次訪問div集合的length屬性,它都會重新計算文檔中的div元素數目。這就是html集合低效率的來源。要改進代碼就用一個局部變量保存div集合的length屬性: for(var i=0, len=allDivs.length; i<len; i++){...}
訪問HTML集合的length比數組的length要慢,所以要訪問這種集合類的數目length,我們都應該先用一個局部變量去保存它:var len = 集合.length;
另外,訪問數組的元素要比訪問HTML集合的元素要快。所以我們可以先把HTML集合轉換成數組才去進行相應的操作:
//HTML集合轉換成數組 function toArray(coll){ for(var a=[], i=0, len=coll.legnth; i<len; i++){ a[i] = coll[i]; } return a; } //使用 var coll = document.getElementsByTagName("div"); var divs = toArray(coll);
有人可能會問,這樣多用了一個數組副本到底值不值得?這個倒是要看情況吧。不過另外一種選擇,使用局部變量:
function loopColletion(){ var coll = document.getElmentsByTagName("div"), len = coll.length, el = null; for(var i = 0; i<len; i++){ el = coll[i]; //然后訪問局部變量el } }
許多瀏覽器提供了API函數返回元素節點,這些API都是原生的,所以可用的話就盡量用。下圖列舉了一些DOM的屬性:
上圖列舉的所有屬性能被FF,safari,chrome,opera所支持,ie6-8只支持children。
遍歷children比childNodes更快,因為集合項少了。HTML源碼中的空格實際上是文本節點,但他們不包含在children中。
另外還有兩個比較好的選擇器API:document.querySelectorAll()和document.querySelector()。前者接收一個CSS選擇器字符串參數並返回一個NodeList類數組對象而不是返回HTML集合,后者只返回符合查詢條件的第一個節點。很遺憾IE6、7不支持這兩個API。
PS:寫一篇博文不容易啊,花了好幾個小時,不過可以鞏固一下知識,這是一個好習慣。這個博客就是我的學習筆記,O(∩_∩)O哈哈~