重繪是指一些樣式的修改,元素的位置和大小都沒有改變; 重排是指元素的位置或尺寸發生了變化,瀏覽器需要重新計算渲染樹,而新的渲染樹建立後,瀏覽器會重新繪製受影響的元素。
去參加面試總會被問到乙個問題,那就是「向瀏覽器輸入一行url會發生什麼?」,這個問題的答案除了要回答網路方面的知識還牽扯到瀏覽器渲染頁面問題。當我們的瀏覽器接收到從伺服器響應的頁面之後便開始逐行渲染,遇到css的時候會非同步的去計算屬性值,再繼續向下解析dom解析完畢之後形成一顆dom樹,將非同步計算好的樣式(樣式盒子)與dom樹相結合便成為了乙個render樹,再由瀏覽器繪製在頁面上。dom樹與render樹的區別在於:樣式為display:none;的節點會在dom樹中而不在渲染樹中。瀏覽器繪製了之後便開始解析js檔案,根據js來確定是否重繪和重排。
產生重繪的因素:
產生重排的因素:
dom樹的結構變化
獲取某些屬性
瀏覽器視窗尺寸改變
滾動條的出現(會觸發整個頁面的重排)
總之你要知道,js是單執行緒的,重繪和重排會阻塞使用者的操作以及影響網頁的效能,當乙個頁面發生了多次重繪和重排比如寫乙個定時器每500ms改變頁面元素的寬高,那麼這個頁面可能會變得越來越卡頓,我們要盡可能的減少重繪和重排。那麼我們對於dom的優化也是基於這個開始。
減少訪問次數自然是想到快取元素,但是要注意
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++)
// 優化後,將length的值快取起來就不會每次都去查詢length的值
var lis = document.getelementsbytagname('li');
for(var i = 0, len = lis.length; i < len; i++)
複製**
而且由於nodelist是動態變化的,所以如果不快取可能會引起死迴圈,比如一邊新增元素,一邊獲取nodelist的length。
2.1.2 改變選擇器
獲取元素最常見的有兩種方法,getelementsby***()和queryselectorall(),這兩種選擇器區別是很大的,前者是獲取動態集合,後者是獲取靜態集合,舉個例子。
// 假設一開始有2個li
var lis = document.getelementsbytagname('li'); // 動態集合
var ul = document.getelementsbytagname('ul')[0];
for(var i = 0; i < 3; i++)
// 輸出結果:2, 3, 4
// 優化後
var lis = document.queryselectorall('li'); // 靜態集合
var ul = document.getelementsbytagname('ul')[0];
for(var i = 0; i < 3; i++)
// 輸出結果:2, 2, 2
複製**
對靜態集合的操作不會引起對文件的重新查詢,相比於動態集合更加優化。
2.1.3 避免不必要的迴圈
// 優化前
for(var i = 0; i < 10; i++)
// 優化後
var str = '';
for(var i = 0; i < 10; i++)
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++) ;
}
// 事件委託後
var ul = document.getelementsbytagname('ul')[0];
ul.onclick = function(event) ;
複製**
事件委託前我們訪問了lis.length次li,而採用事件委託之後我們只訪問了一次ul。
2.2.1 改變乙個dom節點的多個樣式
我們想改變乙個div元素的寬度和高度,通常做法可以是這樣
var div = document.getelementbyid('div1');
div.style.width = '220px';
div.style.height = '300px';
複製**
以上操作改變了元素的兩個屬性,訪問了三次dom,觸發兩次重排與兩次重繪。我們說過優化是減少訪問次數以及減少重繪重排次數,從這個出發點可不可以只訪問一次元素以及重排次數降低到1呢?顯然是可以的,我們可以在css裡寫乙個class
/* css
.change
*/document.getelementbyid('div').classname = 'change';
複製**
這樣就達到了一次操作多個樣式
2.2.2 批量修改dom節點樣式
上面**的情況是針對於乙個dom節點的,如果我們要改變乙個dom集合的樣式呢? 第一時間想到的方法是遍歷集合,給每個節點加乙個classname。再想想這樣豈不是訪問了多次dom節點?想想文章開頭說的dom樹和渲染樹的區別,如果乙個節點的display屬性為none那麼這個節點不會存在於render樹中,意味著對這個節點的操作也不會影響render樹進而不會引起重繪和重排,基於這個思路我們可以實現優化:
// 假設增加的class為.change
var lis = document.getelementsbytagname('li');
var ul = document.getelementsbytagname('ul')[0];
ul.style.display = 'none';
for(var i = 0; i < lis.length; i++)
ul.style.display = 'block';
複製**
2.2.3 documentfragment
createdocumentfragment()方是用了建立乙個虛擬的節點物件,或者說,是用來建立文件碎片節點。它可以包含各種型別的節點,在建立之初是空的。
documentfragment節點不屬於文件樹,繼承的parentnode屬性總是null。它有乙個很實用的特點,當請求把乙個documentfragment節點插入文件樹時,插入的不是documentfragment自身,而是它的所有子孫節點。這個特性使得documentfragment成了佔位符,暫時存放那些一次插入文件的節點
另外,當需要新增多個dom元素時,如果先將這些元素新增到documentfragment中,再統一將documentfragment新增到頁面,會減少頁面渲染dom的次數,效率會明顯提公升。
使用方法:
var frag = document.createdocumentfragment(); //建立乙個dom片段
for(var i=0;i<10000;i++)
複製**
減少重繪與重排
DOM優化 筆記
webilt型別的瀏覽器 dom 方法要比 innerhtml 要好,而別的瀏覽器則是innerhtml效能要比dom要好。儘量減少dom操作 節點轉殖,訪問元素集合 盡量使用區域性變數 元素節點,選擇器api console.time hello3 var oli document.createe...
DOM解析和優化
dom解析 dom優化 dom操作會導致repaint和reflow,減少repaint和reflow可以優化效能。1.合併多次dom操作為一次 element.style.bordercolor f00 element.style.borderstyle solid element.style.b...
javascript基礎六 (DOM優化)
dom優化 1 innerhtml與dom操作方法,那個效率會更高 chrome dom方法要比innerhtml的方法要好 firefox 正好相反 2 clonenode複製節點要比建立新節點,效能優化的多 3 減少dom的頻繁操作 4 新的選擇器queryselectorall dmon與瀏覽...