關於DOM的操作以及性能優化問題-重繪重排


寫在前面:

  大家都知道DOM的操作很昂貴。 

  然后貴在什么地方呢? 

  一、訪問DOM元素

  二、修改DOM引起的重繪重排

一、訪問DOM  

  像書上的比喻:把DOM和JavaScript(這里指ECMScript)各自想象為一個島嶼,它們之間用收費橋梁連接,ECMAScript每次訪問DOM,都要途徑這座橋,並交納“過橋費”,訪問DOM的次數越多,費用也就越高。因此,推薦的做法是盡量減少過橋的次數,努力待在ECMAScript島上。我們不可能不用DOM的接口,那么,怎樣才能提高程序的效率?

  1. 既然無法避免,那就減少訪問。(width、offsetTop、left。。。能少就少,可以緩存起來的,就緩存)
    復制代碼
    // code1錯誤
    console.time(1);
    for(var i = 0; i < times; i++) {
     document.getElementById('div1').innerHTML += 'a';
    }
    console.timeEnd(1);
     
    // code2正確
    console.time(2);
    var str = '';
    for(var i = 0; i < times; i++) {
     str += 'a';
    }
    document.getElementById('div2').innerHTML = str;
    console.timeEnd(2);
    ////////////////////////
    復制代碼
  2. html集合&遍歷DOM

     html集合類似數組,但是跟數組還是不一樣的。如: document.getElementsByTagName('a') 返回的html集合。這個集合是實時更新的,即后面代碼修改了DOM,會反映在這個html集合里面。可嘗試代碼。

復制代碼
<body>
 <ul id='fruit'>
 <li> apple </li>
 <li> orange </li>
 <li> banana </li>
 </ul>
</body>
<script type="text/javascript">
 var lis = document.getElementsByTagName('li');
 var peach = document.createElement('li');
 peach.innerHTML = 'peach';
 document.getElementById('fruit').appendChild(peach);
 
 console.log(lis.length); // 4
</script>
復制代碼

 

    正因為這個原因:html集合,讀取 length 屬性比數組消耗大多了。

    要解決這個問題並不難,在遍歷DOM集合的時候,緩存length就好了。不要每次使用就獲取,主要體現在for循環中(你應該知道,for循環中,每一次都會執行判讀語句,讀取length)

復制代碼
console.time(0);
var lis0 = document.getElementsByTagName('li');
var str0 = '';
for(var i = 0; i < lis0.length; i++) {
 str0 += lis0[i].innerHTML;
}
console.timeEnd(0);
 
console.time(1);
var lis1 = document.getElementsByTagName('li');
var str1 = '';
for(var i = 0, len = lis1.length; i < len; i++) {
 str1 += lis1[i].innerHTML;
}
console.timeEnd(1);
復制代碼

 

 

 二、重繪重排

  1.什么是重繪重排?

   瀏覽器下載完頁面中的所有組件——HTML標記、JavaScript、CSS、圖片之后會解析生成兩個內部數據結構——DOM樹渲染樹

   在文檔初次加載時,瀏覽器引擎通過解析 html文檔 構建一棵DOM樹,之后根據DOM元素的幾何屬性構建一棵用於展示渲染的渲染樹。渲染樹中的節點被稱為“幀”或“盒",符合CSS模型的定義,可理解為(包括理解頁面元素為一個具有大小,填充,邊距,邊框和位置的盒子)。由於隱藏元素不需要顯示,渲染樹中並不包含DOM樹中隱藏的元素(知道這點有用)。 當渲染樹構建完成,瀏覽器把每一個元素放到正確的位置上,然后再根據每一個元素的其他樣式,繪制頁面。

   由於瀏覽器的流布局,對渲染樹的計算通常只需要遍歷一次就可以完成。但table及其內部元素除外,它可能需要多次計算才能確定好其在渲染樹中節點的屬性,通常要花3倍於同等元素的時間。這也是為什么我們要避免使用table做布局的一個原因。

   重繪:是一個元素外觀的改變所觸發的瀏覽器行為,例如改變visibility、outline、背景色等屬性(上面說到的其他屬性)。瀏覽器會根據元素的新屬性重新繪制,使元素呈現新的外觀。重繪不會帶來重新布局,並不一定伴隨重排。

  重排:當DOM的變化影響了元素的幾何屬性(寬或高),瀏覽器需要重新計算元素的幾何屬性,同樣其他元素的幾何屬性和位置也會因此受到影響。瀏覽器會使渲染樹中受到影響的部分失效,並重新構造渲染樹。這個過程稱為重排。重排一定伴隨着重繪。

 

  2. 觸發重排的操作:

  2.1 修改DOM元素幾何屬性

    修改元素大小,位置,內容(一般只有重繪,但是內容可能導致元素大小變化)

  2.2 DOM樹結構發生變化

    當DOM樹的結構變化時,例如節點的增減、移動等,也會觸發重排。瀏覽器引擎布局的過程,類似於樹的前序遍歷,是一個從上到下從左到右的過程。 通常在這個過程中,當前元素不會再影響其前面已經遍歷過的元素。所以,如果在body最前面插入一個元素,會導致整個文檔的重新渲染,而在其后插入一個元 素,則不會影響到前面的元素。

    2.4 改變瀏覽器大小

   

  3.渲染樹變化的排隊和刷新

   思考下面代碼:

1 var ele = document.getElementById('myDiv');
2 ele.style.borderLeft = '1px';
3 ele.style.borderRight = '2px';
4 // var _top = ele.offsetTop; //刷新隊列
5 ele.style.padding = '5px';

 

  三行代碼,三次修改元素的幾何屬性,瀏覽器應該發生三次重排重繪。

  但是瀏覽器並不會這么笨,它也是有做優化的。它會把三次修改“保存”起來(大多數瀏覽器通過隊列化修改並批量執行來優化重排過程,也有設置時間片段的),一次完成!

  然而,如果你在三行代碼中,以下獲取DOM布局信息。(為了返回最新的布局信息,將立即執行渲染樹變化隊列的更新)

  如上面被注釋的第4行,如果取消注釋會導致(2+3)、(5)兩次重排;

  獲取關於DOM布局信息的屬性:

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop, scrollLeft, scrollWidth, scrollHeight
  3. clientTop, clientLeft, clientWidth, clientHeight
  4. getComputedStyle() (currentStyle in IE)

 

  4 應對方法:盡量減少重繪次數、減少重排次數、縮小重排的影響范圍。

   4.1 合並多次操作,如上面的操作

ele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

  4.2 將需要多次重排的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設置為絕對定位。 

    4.3 由於display屬性為none的元素不在渲染樹中,對隱藏的元素操作不會引發其他元素的重排。如果要對一個元素進行復雜的操作時,可以先隱藏它,操作完成后再顯示。這樣只在隱藏和顯示時觸發2次重排。但是這可能導致瀏覽器的閃爍。

  4.4 在內存中多次操作節點,完成后再添加到文檔中去(可使用fragment元素)。例如要異步獲取表格數據,渲染到頁面。可以先取得數據后在內存中構建整個表格的html片段,再一次性添加到文檔中去,而不是循環添加每一行。

復制代碼
var fragment = document.createDocumentFragment();    // 未使用的虛擬節點,appendChild(fragment)  //append的是里面的子元素
 
var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);
 
var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);
 
document.getElementById('fruit').appendChild(fragment);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM