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

 寫在前面:javascript

  你們都知道DOM的操做很昂貴。 css

  而後貴在什麼地方呢? html

  1、訪問DOM元素前端

  2、修改DOM引發的重繪重排java

1、訪問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)app

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);

 

 

 2、重繪重排

  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);

 

 

 參考文檔:

相關文章
相關標籤/搜索