首頁 > web前端 > js教程 > 主體

JavaScript之優化DOM

php中世界最好的语言
發布: 2018-03-19 16:27:09
原創
1821 人瀏覽過

這次帶給大家JavaScript之優化DOM,JavaScript之優化DOM的注意事項有哪些,下面就是實戰案例,一起來看一下。

最佳化DOM得從重繪和重排講起,long long ago...

#1、重繪和重排

1.1 重繪和重排是什麼

重繪是指一些樣式的修改,元素的位置和大小都沒有改變;

重排是指元素的位置或尺寸發生了變化,瀏覽器需要重新計算渲染樹,而新的渲染樹建立後,瀏覽器會重新繪製受影響的元素。

1.2 瀏覽器渲染頁面

去參加面試總會被問到一個問題,那就是「向瀏覽器輸入一行url會發生什麼? ”,這個問題的答案除了要回答網絡方面的知識還牽扯到瀏覽器渲染頁面問題。當我們的瀏覽器接收到從伺服器回應的頁面之後便開始逐行渲染,遇到css的時候會異步的去計算屬性值,再繼續向下解析dom解析完畢之後形成一顆DOM樹,將異步計算好的樣式(樣式盒子)與DOM樹結合便成為了一個Render樹,再由瀏覽器繪製在頁面上。 DOM樹與Render樹的差別在於:樣式為display:none;的節點會在DOM樹而不在渲染樹。 瀏覽器繪製了之後便開始解析js文件,根據js來決定是否重繪和重排。

1.3 造成重繪和重排的原因

#產生重繪的因素:

  • 改變visibility、outline、背景色等樣式屬性,並沒有改變元素大小、位置等。瀏覽器會根據元素的新屬性重新繪製。

產生重排的因素:

  • 內容改變

    • 文字改變或圖片尺寸改變

  • DOM元素的幾何屬性的變化

    • 例如改變DOM元素的寬高值時,原始渲染樹中的相關節點會失效,瀏覽器會根據變化後的DOM重新排建渲染樹中的相關節點。若父節點的幾何屬性改變時,也會使其子節點及後續兄弟節點重新計算位置等,造成一連串的重排。

  • DOM樹的結構變化

    • #新增DOM節點、修改DOM節點位置及刪除某個節點都是DOM樹的更改,會造成頁面的重排。瀏覽器佈局是從上到下的過程,修改當前元素不會對其前邊已經遍歷過的元素造成影響,但是如果在所有的節點前面添加一個新的元素,則後續的所有元素都要進行重排。

  • 取得某些屬性

    • #除了渲染樹的直接變化,當取得一些屬性值時,瀏覽器要獲得正確的值也會重排,這些屬性包括:offsetTopoffsetLeft、 offsetWidthoffsetHeight# scrollTopscrollLeftscrollWidthscrollHeight、 clientTopclientLeftclientWidthclientHeightgetComputedStyle()

  • 瀏覽器視窗尺寸改變

    • #視窗尺寸的改變會影響整個網頁內元素的尺寸的改變,即DOM元素的集合屬性變化,因此會造成重排。

  • 捲軸的出現(會觸發整個頁面的重排)

總你要知道,js是單一執行緒的,重繪和重排會阻塞使用者的操作以及影響網頁的效能,當一個頁面發生了多次重繪和重排比如寫一個定時器每500ms改變頁面元素的寬高,那麼這個頁面可能會變得越來越卡頓,我們要盡可能的減少重繪和重排。那我們對於DOM的最佳化也是基於這個開始。

2、最佳化  

2.1 減少存取

減少造訪次數自然是想到快取元素,但要注意

#
var ele = document.getElementById('ele');
登入後複製

這樣並不是對ele進行緩存,每一次呼叫ele還是相當於訪問了一次id為ele的節點。

2.1.1 快取NodeList

var foods = document.getElementsByClassName('food');
登入後複製

我們可以用foods[i]來存取第i個class為food的元素,不過這裡的foods並不是一個陣列,而是一個NodeList。 NodeList是一個類別數組,保存了一些有序的節點並且可以透過位置來存取這些節點。 NodeList物件是動態的,每一次存取都會執行一次基於文件的查詢。所以我們要盡量減少造訪NodeList的次數,可以考慮將NodeList的值快取起來。

// 优化前var lis = document.getElementsByTagName('li');for(var i = 0; i < lis.length; i++) {     // do something...  }// 优化后,将length的值缓存起来就不会每次都去查询length的值var lis = document.getElementsByTagName('li');for(var i = 0, len = lis.length; i < len; i++) {     // do something...  }
登入後複製

而且由於NodeList是動態變化的,所以如果不快取可能會造成死循環,例如一邊加入元素,一邊取得NodeList的length。

2.1.2 改變選擇器

取得元素最常見的有兩種方法,getElementsByXXX()和queryselectorAll(),這兩種選擇器差異是很大的,前者是取得動態集合,後者是取得靜態集合,舉例。

// 假设一开始有2个livar lis = document.getElementsByTagName('li');  // 动态集合var ul = document.getElementsByTagName('ul')[0]; 
for(var i = 0; i < 3; i++) {
    console.log(lis.length);    var newLi = document.createElement('li'); 
    ul.appendChild(newLi);
}// 输出结果:2, 3, 4var lis = document.querySelector('li');  // 静态集合 var ul = document.getElementsByTagName('ul')[0]; 
for(var i = 0; i < 3; i++) {
    console.log(lis.length);    var newLi = document.createElement('li'); 
    ul.appendChild(newLi);
}// 输出结果:2, 2, 2
登入後複製

對靜態集合的操作不會造成對文件的重新查詢,相較於動態集合更加最佳化。

2.1.3 避免不必要的循環

// 优化前
for(var i = 0; i < 10; i++) {
    document.getElementById('ele').innerHTML += 'a';
登入後複製
} 
// 优化后 
var str = ''; 
for(var i = 0; i < 10; i++) {
    str += 'a'; 
}
document.getElementById('ele').innerHTML = str;
登入後複製

優化前的程式碼存取了10次ele元素,而優化後的程式碼只訪問了一次,大大的提高了效率。

2.1.4 事件委託

js中的事件函數都是對象,如果事件函數過多會佔用大量內存,而且綁定事件的DOM元素越多會增加訪問dom的次數,對頁面的互動就緒時間也會有延遲。所以誕生了事件委託,事件委託是利用了事件冒泡,只指定一個事件處理程式就可以管理某一類型的所有事件。

// 事件委托前var lis = document.getElementsByTagName('li');for(var i = 0; i < lis.length; i++) {
   lis[i].onclick = function() {
      console.log(this.innerHTML);
   };  
}    
// 事件委托后var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function(event) {
   console.log(event.target.innerHTML);
};
登入後複製

事件委託前我們訪問了lis.length次li,而採用事件委託之後我們只訪問了一次ul。

2.2 減少重繪重排

2.2.1 改變一個dom節點的多個樣式

我們想要改變一個p元素的寬度和高度,通常做法可以是這樣

 p = document.getElementById('p1'
登入後複製

以上操作改變了元素的兩個屬性,訪問了三次dom,觸發兩次重排與兩次重繪。我們說過優化是減少造訪次數以及減少重繪重排次數,從這個出發點可不可以只訪問一次元素以及重排次數降低到1呢?顯然是可以的,我們可以在css裡寫一個class

/* css
.change {
    width: 220px;
    height: 300px;
}*/document.getElementById('p').className = 'change';
登入後複製

這樣就達到了一次操作多個樣式

2.2.2  批量修改dom節點樣式

上面程式碼的情況是針對一個dom節點的,如果我們要改變一個dom集合的樣式呢?

第一時間想到的方法是遍歷集合,為每個節點加上一個className。再想想這樣豈不是訪問了多次dom節點?想想文章開頭說的dom樹和渲染樹的差別,如果一個節點的display屬性為none那麼這個節點不會存在於render樹中,代表對這個節點的操作也不會影響render樹進而不會引起重繪和重排,基於這個思路我們可以實現優化:

  • 將待修改的集合的父元素display: none;

  • 之后遍历修改集合节点

  • 将集合父元素display: block;

// 假设增加的class为.changevar lis = document.getElementsByTagName('li');  
var ul = document.getElementsByTagName('ul')[0];
ul.style.display = 'none';for(var i = 0; i < lis.length; i++) {
    lis[i].className = 'change';  
}
ul.style.display = 'block';
登入後複製

3、总结

  • 减少访问dom的次数

    • 缓存节点属性值

    • 选择器的使用

    • 避免不必要的循环

    • 事件委托

  • 减少重绘与重排

    • 使用className改变多个样式

    • 使父元素脱离文档流再恢复

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

Vue的计算属性

Webpack模块的使用

以上是JavaScript之優化DOM的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!