高性能JavaScript之DOM篇

本篇文章主要分享一下操做DOM時的一些細節,來提升頁面性能。
首先咱們來思考如下幾個問題。
1.如何獲取頁面中全部class爲div1和div2的div元素。
2.你瞭解HTMLCollection和NodeList嗎?有什麼區別?
3.如何獲取一個元素的css屬性值?
4.怎麼減小重排和重繪?

問題一:如何獲取頁面中全部class爲div1和div2的div元素。
這個問題若是我之前寫的話,我會這麼寫。
var errs = [],
    divs = document.getElementsByTagName('div'),
    className = '';
for(var i = 0 , len = divs.length; i < len; i ++ ){
    className = divs[i].className;
    if (className === 'div1' || className === 'div2') {
        errs.push(divs[i]);
    }
}
看起來中規中矩,好像沒什麼大問題。
可是如今我會這麼寫
var errs2 = document.querySelectorAll('div.div1, div.div2');
用querySelectorAll,表面上看縮短了不少代碼量,其實它的做用還不止這個。接下來咱們引出第二個問題。

問題二:你瞭解HTMLCollection和NodeList嗎?有什麼區別?
咱們先來看兩段代碼。想一下這兩段代碼有什麼區別。
// 片斷一
var all = document.getElementsByTagName('div');
for(var i = 0; i < all.length ; i ++) {
    document.body.appendChild(document.createElement('div'));
}
// 片斷二
var all2 = document.querySelectorAll('div');
for(var i = 0; i < all2.length ; i ++) {
    document.body.appendChild(document.createElement('div'));
}

你能夠分別複製一下這兩段代碼在你的瀏覽器中運行,看看有什麼區別沒有。前提是你的頁面中至少有一個div。不然不會進入到循環中去。
運行完以後你會發現,片斷一運行完以後頁面崩潰了。片斷二正常如期運行。爲何呢?由於用getElementsByTagName方法獲取到的是HTMLCollection,而HTMLCollection是實時獲取的,也就是說,每次循環時請求all.length,都會從新去觸發一次getElementsByTagName方法。致使all.length每次循環以後都會加1。最終形成了死循環。而用querySelectorAll方法獲取到的結果是NodeList,NodeList不是實時獲取的。也正是由於這個緣由,咱們推薦使用querySelector去代替getElementBy方法。實時獲取會形成頁面不斷重排,這極大的下降了性能。另外:除了getElementByXXX方法外,document.images/document.links等方法返回的也是HTMLCollection。css


問題三:如何獲取一個元素的css屬性值?

咱們知道,直接用div.style.color這樣的方式去獲取css樣式只能獲取到行內寫的屬性,寫在style標籤中的樣式是獲取不到的。標準瀏覽器中有一個window.getComputedStyle方法,能夠獲取實時的樣式(在IE中是currentStyle)。可是這個方法也會引發頁面的重排,由於只有把頁面重排以後他纔會獲取到實時的樣式。瀏覽器


問題四:如何減小重排和重繪次數。

在改變DOM的一些樣式時,重排和重繪是沒法避免的,可是咱們要儘可能讓這個次數下降,下面介紹集中方法。
1.當須要給一個DOM元素加各類動畫或者改變一系列樣式時,先把這個元素設置成絕對定位。由於脫離文檔流後,再對這個元素進行操做就不會影響到他周圍的元素,也就不會發生重排。等到一系列複雜的操做結束以後,再把這個元素的定位恢復到以前的樣子。這樣只進行了兩次重排,能明顯的提升性能。
2.當咱們須要改變不少樣式時,不要用style.color,style.width,style.height這樣一個一個去改,由於每次修改都會引發一次重排。咱們應該使用給這個元素加一個類名,把全部的樣式都寫到這個類名中。或者是改style.cssText。這樣能夠有效的減小重排的次數。
3.用fragment代替dom。思考這樣一個問題,如何給頁面中插入10000個div元素。每次建立一個而後append到body中去嗎?這顯然不是一個好的辦法。咱們應該先把全部建立的元素都添加到一個fragment中。而後再把fragment添加到body中去。這樣只會引發一次重排,而不是1萬次。
4.介紹一些會引發重排的屬性。當訪問元素的如下屬性時也會引發重排,由於這些屬性是實時算的。緩存

  • offsetTop,offsetLeft,offsetWidth,offsetHeight
  • scrollTop,scrollLeft,scrollWidth,scrollHeight
  • clientTop,clientLeft,clientWidth,clientHeight

5.儘量少的訪問DOM。看下面兩段代碼。app

// 片斷一
console.time('test1')
for(let i = 0; i < 1000; i++) {
    document.getElementById('div1').innerHTML+= i;
}
console.timeEnd('test1'); // 打印結果80ms左右

// 片斷二
console.time('test3')
let count = ''
for(let i = 0; i < 1000; i++) {
    count += i;
}
document.getElementById('div1').innerHTML+= count;
console.timeEnd('test3'); // 打印結果0.3ms左右

一個是不停的訪問DOM,並修改內容,一個是先把內容緩存起來,只訪問一次。打印出來時間相差好幾百倍。dom


最後:說幾個能夠提升效率和性能的小細節。

用正確的DOM屬性。性能

// 由於咱們大部分時候須要操做的是元素節點,像什麼文本節點,註釋節點通常都會過濾掉。若是是這樣的話,提倡用前面的屬性代替後面的屬性。
// children childNodes
// childElementCount childNodes.length
// firstElementChild firstChild
// lastElementChild lastChild
// nextElementSibling nextSibling
// previousElementSibling previousSibling

須要給大量元素分發事件時,採起事件委託。由於瀏覽器不只不須要分發不少事件,並且須要跟蹤每一個事件處理器,這也會佔用更多內存。動畫

相關文章
相關標籤/搜索