由重排重繪引起對虛擬DOM以及性能優化的思考

重排重繪

  • 重排:當DOM的變化影響了元素的幾何屬性(寬和高),瀏覽器須要從新計算元素的幾何屬性,一樣其餘元素的幾何屬性和位置也會所以受到影響。瀏覽器會使渲染樹中收到影響的部分失效,並從新構造渲染樹,這個過程成爲重排。
  • 重繪:完成重排後,瀏覽器會從新繪製受影響的部分到屏幕,該過程稱重繪。
  • 重排重繪的危害:它們會致使Web應用程序的UI反應遲鈍,因此應當儘量減小這類過程的發生。
  • 觸發重排的狀況
    • 添加或刪除可見的DOM元素
    • 元素位置的改變
    • 元素尺寸的改變
    • 元素內容的改變(例如:一個文本被另外一個不一樣尺寸的圖片代替)
    • 頁面渲染初始化(這個沒法避免)
    • 瀏覽器窗口尺寸改變
  • 觸發重繪的狀況
    • 字體顏色
    • 背景顏色
  • 優化
    • 讀寫分離操做
      div.style.top = '10px';
      div.style.bottom = '10px';
      console.log(div.offsetWidth);
      console.log(div.offsetHeight);
      複製代碼
    • 樣式集中改變
      • 經過class或cssText
      var left = 10;
      var top = 10;
      el.style.marginLeft = left + 'px';
      el.style.marginTop = top + 'px';
      
      // class
      .className{
        margin-left: 10px;
        margin-top: 10px;
      }
      el.className += ' className';
      // cssText
      var left = 10;
      var top = 10;
      div.style.cssText += 'margin-left: ' + Left + 'px; margin-top: ' + Top + 'px;'
      複製代碼
    • 緩存佈局信息
      // 觸發兩次重排
      div.style.left = div.offsetLeft + 1 + 'px';
      div.style.top = div.offsetTop + 1 + 'px';
      
      // 緩存佈局信息 至關於讀寫分離
      var curLeft = div.offsetLeft;
      var curTop = div.offsetTop;
      div.style.left = curLeft + 1 + 'px';
      div.style.top = curTop + 1 + 'px';
      複製代碼
    • 離線改變dom
      • 一旦咱們給元素設置display: none;時,元素不會存在渲染樹中,至關於將其從頁面"拿掉",咱們以後的操做將不會觸發重排和重繪,這叫作離線化。
      dom.display = 'none'
      // 修改dom樣式
      dom.display = 'block';
      複製代碼
      • 經過使用DomcumentFragment建立一個dom碎片,在它上面批量操做dom,操做完成以後,再添加到文檔中,這樣只會觸發一次重排
      • 複製節點,在副本上工做,如何替換成它。
    • position屬性爲absolute或fixed
      • position屬性爲absolute或fixed的元素,重排開銷比較小,不用考慮它對其餘元素的影響。
    • 優化動畫
      • 啓動GPU加速。
        • 將2D的transform換成3D就能夠強制開啓GPU加速,提升動畫性能。
        div {
          transform: translate3d(10px, 10px, 0);
        }
        複製代碼

虛擬DOM

  • 什麼是虛擬DOM?css

    • 能夠看作是一個使用JavaScript模擬了DOM結構的樹形結構,這個樹形結構包括整個DOM結構。
  • VDom特色html

    • 虛擬DOM不會進行重排和重繪操做。
    • 虛擬DOM進行頻繁操做,而後一次性修改真實DOM須要改的部分,最後並在真實的DOM中進行排版與重繪,減小過多DOM節點排版和重繪消耗。
    • 真實DOM頻繁排版和重繪的效率是至關低的。
    • 虛擬DOM有效的下降大面積(真實DOM節點)的重排和排版,由於最終與真實DOM比較差別,能夠只渲染局部。
  • 爲何使用虛擬DOM?前端

    • 以前使用原生JS或者jQuery寫頁面的時候會發現操做DOM是一件很是麻煩的事情,每每是DOM標籤和JS邏輯同時寫在JS文件裏,數據交互是否是還要寫不少的input隱藏域。若是沒有好的代碼規範的話會顯得代碼很是冗餘混亂,耦合性高而且難以維護。
    • 另外一個方面在瀏覽器一遍又一遍的渲染DOM是很是很是消耗性能的,經常會出現頁面卡死的狀況。因此儘可能減小對DOM的操做成爲了優化前端性能的必要手段,vdom就是將DOM的對比放在了JS層,經過對比不一樣之處來選擇新渲染DOM節點,從而提升渲染效率。
  • 使用虛擬DOM的損耗計算node

    總消耗 = 虛擬DOM增刪改 + (與Diff算法效率有關)真實DOM差別增刪改 + (較少的節點)排版與重繪
    複製代碼
  • 使用真實DOM的損耗計算webpack

    總損耗 = 真實DOM徹底增刪改 + (可能較多的節點)排版與重繪
    複製代碼
  • Diff算法(實現虛擬DOM對真實的DOM的更新)web

    function updateChildren(vnode, newVnode) { // 建立對比函數
      var children = vnode.children || [];
      var newChildren = newVnode.children || [];
    
      children.forEach(function(childrenVnode, index) {
        var newChilrenVnode = newChildren[index]; // 首先拿到對應新的節點
        if(childrenVnode.tag = newChildrenVnode.tag) { // 判斷節點是否相同
          updateChildren(childrenVnode, newChildVnode); // 若是相同就執行遞歸,深度對比節點
        }else {
          replaceNode(childrenVnode, newChildVnode); // 若是不一樣則將舊的節點替換成新的節點
        }
      })
    }
    function replaceNode(vnode, newVnode) { // 節點替換函數
      var elem = vnode.elem;
      var newEle = createElement(newVnode);
    }
    複製代碼

性能優化

  • 減小HTTP請求(最重要最有效)
    • 一個完整的請求都須要DNS尋址丶與服務器創建連接丶發送數據丶等待服務器響應丶接受數據這樣一個漫長而複雜的過程。因爲瀏覽器進行併發請求的請求數都是有上限的,所以請求數多了之後,瀏覽器須要進行分批請求,所以會增長用戶的等待時間,給用戶形成網站速度慢這種現象,即便可能用戶看到的第一屏的資源都已經請求完了,可是瀏覽器的進度條一直在加載。
  • 請求優化(減小HTTP請求數的途徑只要有如下幾個)
    • 從設計層面實現優化頁面
      • 若是你的頁面和百度的頁面一眼簡單,那麼也就不須要什麼優化操做了,所以保持頁面簡潔丶減小資源的使用是最直接的。
    • 合理設置HTTP緩存
      • 緩存的力量是強大的,恰當的緩存設置能夠大大減小HTTP請求。
      • 怎麼樣纔是合理的緩存,原則很簡單:能緩存越多越好。
      • 例如:不多變化的圖片資源就能夠直接經過HTTP Header中的Expires設置一個很長的過時頭,變化不頻繁而又可能會變的資源可使用Last-Modified來作請求校驗,儘量的讓資源可以在緩存中待的更久。
    • 資源合併與壓縮
      • 若是能夠的話,儘量的將外部腳本和樣式進行合併,儘量的合併爲一個,另外CSS丶JS丶Image均可以使用相應的工具壓縮,壓縮每每能節省很多空間,或者使用webpack等前端工廠化工具進行代碼的壓縮和去重。
    • CSSprites 雪碧圖
      • 雪碧圖又叫作精靈圖,咱們能夠把網站中須要用到的一些icon所有放在一個圖片資源中,如何經過改變位置來獲取須要的圖片,這樣合併CSS圖片,就能夠大幅度減小HTTP請求數。
    • 網頁內聯圖片 html inline image
      • 使用data: URL scheme的方式將圖片嵌入到頁面或CSS中,若是不考慮資源管理上的問題的話,不失爲一個好辦法,若是是嵌入頁面的話換來的是增大了頁面的體積,而不法利用好瀏覽器緩存,使用CSS中的圖片的更加理想一些。
    • 懶加載(Lazy Load Image)
      • 這條策略實際上並不必定減小HTTP請求數,可是卻能在某些條件下或者頁面剛加載時減小HTTP請求。對於圖片而言,在頁面加載的時候只加載第一屏,當用戶繼續日後滾屏的時候才加載後續的圖片,這樣一來,假如用戶只對第一屏的內容感興趣的時,那剩餘的圖片請求都節省了。有的首頁曾經的作法是在加載的時候把第一屏的以後的圖片地址緩存在Textarea中,待用戶往下滾屏的時候才"惰性"加載。
    • 瀑布流
      • 其實懶加載並不能減小HTTP請求數,它只是減小頁面剛加載的時候的HTTP請求數,總數是不變的。對於圖片而言,在頁面加載的時候可能只加載第一屏的圖片,隨着用戶的滾動纔會加載後面的圖片資源,這種瀑布流的加載方式就能夠有效提升性能。
    • 減小沒必要要的HTTP跳轉
      • 對於以目錄形式訪問的HTTP連接,不少人都會忽略最後是否帶了"/"加入,服務器對此區別對待的話,那麼你也要注意了,這其中可能隱藏了301跳轉,增長了多餘請求。
    • 避免重複的資源請求
      • 這種狀況主要是因爲在模塊化開發的時候,咱們不一樣的模塊之間可能會有相同的部分,致使資源的重複請求。
  • 渲染優化
    • 將外部腳本放在底部
      • 前文有談到,瀏覽器是能夠併發請求的。這一特色使得其可以更快的加載資源,然而外部腳本在加載的時卻會阻塞其餘資源。例如在腳本加載完成以前,它後面的圖片丶樣式以及其餘的腳本卻處於阻塞狀態,直至腳本加載完成後纔會加載。若是將腳本放在靠前的位置,則會影響整個頁面的加載速度從而影響用戶體驗,減小這一問題的方法有不少,而最簡單可依賴的方法就是將腳本儘量的日後挪,減小對併發下載的影響。
    • 將CSS放在Head中
      • 若是將CSS放在其餘的地方好比body中,則瀏覽器有可能未下載和解析到CSS就開始渲染頁面了,這就致使由無CSS狀態跳轉到CSS狀態,用戶體驗比較糟糕。除此以外,有些瀏覽器會在CSS下載完成纔開始渲染頁面,若是CSS放在靠下的位置則會致使瀏覽器將渲染事件推遲。
    • 慎用with
      • with會改變做用域鏈,有可能致使咱們的做用域鏈變長,致使查詢性能降低。
    • 避免使用eval和Function
      • 每次eval或Function構造函數做用於字符串表示的源代碼的時,腳本引擎都須要將源代碼轉換成可執行的代碼,這是很耗費資源的操做。
      • 一般比簡單的函數調研慢100倍以上
    • 減小做用域鏈查找
      • 若是在循環中須要訪問非做用域下的變量的時候,請遍歷以前用局部變量緩存變量,並在遍歷結束以後重寫緩存變量。
    • 減小數據訪問
      • js中對直接量和局部變量的訪問時最快的,對對象屬性以及數組的訪問須要更大的開銷,當出現下面的狀況的時候,建議將數據放在局部變量。
        • 對任何對象屬性的訪問超過一次
        • 對任何數組成員的訪問次數超過1次
        • 另外,要儘量的減小對對象以及數組的深度查找。
    • 減小字符串的拼接
      • 字符串拼接儘量少的使用"+",這種方式的效率是十分低下的,由於每次運行都會開闢新的內存並生成新的字符串變量,而後將拼接的結果賦值給新變量。
      • 建議使用的是先轉化爲數組,若是經過數組的join方法來鏈接成字符串,不過因爲數組也有必定的開銷,所以須要權衡一下,當拼接的字符串比較少的時候,能夠考慮用"+"的方式,比較多的時候就須要考慮用數組的json方法。
    • 減小DOM操做(DOM操做應該是腳本中最耗費性能的一類操做)
      • HTMLCollection(HTML收集器 返回的是一個數組的內容信息)
      • 由於是這個集合並非一個靜態的集合,它表示的僅僅是一個特定的查詢,每次訪問該集合時都會從新執行這個,查詢從而更新查詢結果,所謂的"訪問集合"包括讀取集合的length屬性丶訪問集合中的元素。
    • 避免重排查繪
      • 頁面有三個樹:DOMTree丶CSSTree丶renderTree(實際上多餘三個),renderTree上有兩個規則,重排(reflow)和重繪(repaint)。
      • 重繪是元素自身的位置和寬高不變,只改變顏色之類的屬性而不會致使後面的元素位置的變化的位置。
      • renderTree發生的動做,重排是元素自身的位置或寬高改變了從而致使的整個頁面的大範圍移動的時候,renderTree發生的動做,因此咱們在DOM操做的時候,要儘可能避免重排。
  • CSS優化
    • 慎用選擇高消耗的樣式,由於高消耗屬性在繪製前須要瀏覽器須要大量計算。
      • box-shadow
      • border-radius
      • transform
    • 避免過度重排,由於發生重排的時候,瀏覽器須要從新計算佈局的位置與大小
      • 常見的重排元素
        • width
        • height
        • padding
        • margin
        • display
        • border-width
        • position
        • float
        • ...
    • 正確使用display屬性,由於display屬性會影響頁面渲染。
      • display: inline;後不該該使用width/height/margin/padding/float
      • display: inline-bloack;後不該該使用float
      • display: block;後不該該使用display: vertical-align;
    • 不濫用float,由於float是渲染時計算量比較大,儘可能減小使用。
    • 動畫性能優化
      • 動畫實現的原理是利用了人眼的"視覺暫停"現象,在短期內連續播放 數幅靜止的畫面,使肉眼因視覺殘象產生錯覺,而誤覺得畫面在動。
      • 動畫的基本概念
        • 幀:在動畫過程當中,每一幅靜止的畫面即爲一"幀"
        • 幀頻:即每秒中播放的靜止畫面的停留時間,單位爲fps(frame per second)
        • 幀時長:即每一幅靜止畫面停留時間,單位通常是ms(毫秒)
        • 跳幀(掉幀/丟幀):在幀頻固定的動畫中,某一幀的時長遠高於平均幀時長,致使其後續數幀被擠壓丟失的現象。
        • 通常瀏覽器的渲染的渲染幀頻是60fps,因此在網頁當中,幀頻若是達到50-60fps將會至關流暢,讓人感到舒暢。
      • 若是使用基於JavaScript的動畫,儘可能使用requestAnimationFrame避免使用setTimout/setInterval
      • 避免經過相似jQuery animation style改變幀頻的樣式,使用CSS聲明,動畫會獲得更好的瀏覽器優化。
      • 使用translate取代absolute定位就會獲得更好的fps,動畫會更加流暢。
    • 多使用硬件能力,如經過3D變形加速開啓GPU加速。
    • 提升CSS選擇器性能
      • 由於CSS選擇器對性能的影響源於瀏覽器匹配選擇器和文檔元素時所消耗的時間,因此優化選擇器的原則是應儘可能避免使用消耗更多的匹配時間的選擇器,CSS選擇器是從右往左進行規則匹配的。
      • 減小搜索個數
        • 多使用類選擇器少使用標籤選擇器
      • 減小層數
        • 使用BEM的命方式
        • 使用面向對象的命名方式

你的點贊是我持續輸出的動力 但願能幫助到你們 互相學習 有任何問題下面留言 必定回覆

相關文章
相關標籤/搜索