<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          淺析前端頁面渲染機制

          時間:?2017-11-21閱讀:?902標簽:?機制

          作為一個前端開發,最常見的運行環境應該是瀏覽器吧,為了更好的通過瀏覽器把優秀的產品帶給用戶,也為了更好的發展自己的前端職業之路,有必要了解從我們在瀏覽器地址欄輸入網址到看到頁面這期間瀏覽器是如何進行工作的,進而了解如何更好的優化實踐,本篇主要圍繞這兩點展開闡述。前端頁面渲染機制可謂是老生常談,但又很有必要再談的話題,于是還是決定寫一篇,即是對知識的回顧總結,又能與大家分享,何樂而不為。網上相關類型的文章也很多,有興趣的可以多學習一下。 


          瀏覽器

          在介紹瀏覽器工作流程之前,先了解一下主流瀏覽器的基礎結構,本文所介紹的瀏覽器主要為開源的Chrome,FireFox及部分開源的Safari,這也是目前市場占比最高的幾大瀏覽器,以本人博客網站為例,可以大致看出各瀏覽器使用比例:

          瀏覽器占比

          瀏覽器基礎結構

          瀏覽器基礎結構主要包括如下7部分:

          1.用戶界面(User Interface):用戶所看到及與之交互的功能組件,如地址欄,返回,前進按鈕等;2.瀏覽器引擎(Browser engine):負責控制和管理下一級的渲染引擎;3.渲染引擎(Rendering engine):負責解析用戶請求的內容(如HTML或XML,渲染引擎會解析HTML或XML,以及相關CSS,然后返回解析后的內容);4.網絡(Networking):負責處理網絡相關的事務,如HTTP請求等;5.UI后端(UI backend):負責繪制提示框等瀏覽器組件,其底層使用的是操作系統的用戶接口;6.JavaScript解釋器(JavaScript interpreter):負責解析和執行JavaScript代碼;7.數據存儲(Data storage):負責持久存儲諸如cookie和緩存等應用數據。

          瀏覽器基礎結構

          瀏覽器內核

          各大主要瀏覽器使用內核也是有差別的,大致可以分為以下幾類:

          Trident內核: IEWebkit內核:Chrome,SafariGecko內核:FireFox


          網絡

          當用戶訪問頁面時,瀏覽器需要獲取用戶請求內容,這個過程主要涉及瀏覽器網絡模塊:

          1.用戶在地址欄輸入域名,如baidu.com,DNS(Domain Name System,域名解析系統)服務器根據輸入的域名查找對應IP,然后向該IP地址發起請求;

          DNS

          2.瀏覽器獲得并解析服務器的返回內容(HTTP response);3.瀏覽器加載HTML文件及文件內包含的外部引用文件及圖片,多媒體等資源。

          DNS預解析(DNS PREFETCH)

          瀏覽器DNS解析大多時候較快,且會緩存常用域名的解析值,但是如果網站涉及多域名,在對每一個域名訪問時都需要先解析出IP地址,而我們希望在跳轉或者請求其他域名資源時盡量快,則可以開啟域名預解析,瀏覽器會在空閑時提前解析聲明需要預解析的域名,如:

          域名預解析

          多線程

          我們通常說JavaScript執行是單線程的,但是瀏覽器網絡部分通常是有幾個平行線程同時開啟,但是也會有
          限制,一般為2-6個。


          渲染引擎及關鍵渲染路徑(Critical Rendering Path)

          渲染引擎所做的事是將請求內容展現給我們,默認支持HTML,XML和圖片類型,對于其他諸如PDF等類型的內容則需要安裝相應插件,但瀏覽器的展示工作流程基本是一樣的。

          通過網絡模塊加載到HTML文件后渲染引擎渲染流程如下,這也通常被稱作關鍵渲染路徑(Critical Rendering Path):

          1.構建DOM樹(DOM tree):從上到下解析HTML文檔生成DOM節點樹(DOM tree),也叫內容樹(content tree);2.構建CSSOM(CSS Object Model)樹:加載解析樣式生成CSSOM樹;3.執行JavaScript:加載并執行JavaScript代碼(包括內聯代碼或外聯JavaScript文件);

          4.構建渲染樹(render tree):根據DOM樹和CSSOM樹,生成渲染樹(render tree);

          渲染樹:按順序展示在屏幕上的一系列矩形,這些矩形帶有字體,顏色和尺寸等視覺屬性。

          5.布局(layout):根據渲染樹將節點樹的每一個節點布局在屏幕上的正確位置;

          6.繪制(painting):遍歷渲染樹繪制所有節點,為每一個節點適用對應的樣式,這一過程是通過UI后端模塊完成;

          關鍵渲染路徑

          為了更友好的用戶體驗,瀏覽器會盡可能快的展現內容,而不會等到文檔所有內容到達才開始解析和構建/布局渲染樹,而是每次處理一部分,并展現在屏幕上,這也是為什么我們經常可以看到頁面加載的時候內容是從上到下一點一點展現的。

          流程圖

          Webkit渲染引擎流程如下圖:

          Webkit渲染引擎流程圖

          Gecko渲染引擎流程如下圖:

          Gecko渲染引擎流程圖

          如上圖,Webkit瀏覽器和Gecko瀏覽器渲染流程大致相同,不同的是:

          1.Webkit瀏覽器中的渲染樹(render tree),在Gecko瀏覽器中對應的則是框架樹(frame tree),渲染對象(render object)對應的是框架(frame);2.Webkit中的布局(Layout)過程,在Gecko中稱為回流(Reflow),本質是一樣的,后文會解釋回流的另一層含義–重新布局;3.Gecko中HTML和DOM樹中間多了一層內容池(Content sink),可以理解成生成DOM元素的工廠。

          單線程

          不同于網絡部分的多線程渲染引擎是單線程工作的,意味著渲染流程是一步一步漸進完成的。

          解析文檔(PARSER HTML)

          在詳細介紹瀏覽器渲染文檔之前,先應該理解瀏覽器如何解析文檔:解析文檔的順序,對于CSS和JavaScript如何處理等。

          解析順序

          瀏覽器按從上到下的順序掃描解析文檔;

          解析樣式和腳本

          腳本

          或許是由于通常會在JavaScript腳本中改變文檔DOM結構,于是瀏覽器以同步方式解析,加載和執行腳本,瀏覽器在解析文檔時,當解析到<script>標簽時,會解析其中的腳本(對于外鏈的JavaScript文件,需要先加載該文件內容,再進行解析),然后立即執行,這整個過程都會阻塞文檔解析,直到腳本執行完才會繼續解析文檔。就是說由于腳本是同步加載和執行的,它會阻塞文檔解析,這也解釋了為什么現在通常建議將<script>標簽放在</body>標簽前面,而不是放在<head>標簽里。現在HTML5提供defer和async兩個屬性支持延遲和異步加載JavaScript文件,如:


          <script defer src="script.js">

          改進

          針對上文說的腳本阻塞文檔解析,主流瀏覽器如Chrome和FireFox等都有一些優化,比如在執行腳本時,開啟另一個線程解析剩余的文檔以找出并加載其他的待下載外部資源(不改變主線程的DOM樹,僅優化加載外部資源)。

          樣式

          不同于腳本,瀏覽器對樣式的處理并不會阻塞文檔解析,大概是因為樣式表并不會改變DOM結構。

          樣式表與腳本

          你可能想問樣式是否會阻塞腳本文件的加載執行呢?正常情況是不會的,但是存在一個問題是通常我們會在腳本中請求樣式信息,但是在文檔解析時,如果樣式尚未加載或解析,將會得到錯誤信息,對于這一問題,FireFox瀏覽器和Webkit瀏覽器處理策略不同:

          當存在有樣式文件未被加載和解析時,FireFox瀏覽器會阻塞所有腳本;而Webkit瀏覽器只會阻塞操作了該文件內聲明的樣式屬性的腳本。

          構建DOM樹

          DOM,即文檔對象模型(Document Object Model),DOM樹,即文檔內所有節點構成的一個樹形結構。

          假設瀏覽器獲取返回的如下HTML文檔:


          <!doctype html> <html> <head> <link rel="stylesheet" href="./theme.css"></link> <script src="./config.js"></script> <title>關鍵渲染路徑</title> </head> <body> <h1 class="title">關鍵渲染路徑</h1> <p>關鍵渲染路徑介紹</p> <footer>@copyright2017</footer> </body> </html>

          首先瀏覽器從上到下依次解析文檔構建DOM樹,如下:

          DOM樹

          構建CSSOM樹

          CSSOM,即CSS對象模型(CSS Object Model),CSSOM樹,與DOM樹結構相似,只是另外為每一個節點關聯了樣式信息。

          theme.css樣式內容如下:


          html, body { width: 100%; height: 100%; background-color: #fcfcfc; } .title { font-size: 20px; } .footer { font-size: 12px; color: #aaa; }

          構建CSSOM樹如圖:

          CSSOM樹;

          執行JAVASCRIPT

          上文已經闡述了文檔解析時對腳本的處理,我們得知腳本加載,解析和執行會阻塞文檔解析,而在特殊情況下樣式的加載和解析也會阻塞腳本,所以現在推薦的實踐是<script>標簽放在</body>標簽前面。

          構建渲染樹(RENDER TREE)

          DOM樹和CSSOM樹都構建完了,接著瀏覽器會構建渲染樹:

          渲染樹,代表一個文檔的視覺展示,瀏覽器通過它將文檔內容繪制在瀏覽器窗口,展示給用戶,它由按順序展示在屏幕上的一系列矩形對象組成,這些矩形對象都帶有字體,顏色和尺寸,位置等視覺樣式屬性。對于這些矩對象,FireFox稱之為框架(frame),Webkit瀏覽器稱之為渲染對象(render object, renderer),后文統稱為渲染對象。

          這里把渲染樹節點稱為矩形對象,是因為,每一個渲染對象都代表著其對應DOM節點的CSS盒子,該盒子包含了尺寸,位置等幾何信息,同時它指向一個樣式對象包含其他視覺樣式信息。

          渲染樹與DOM樹

          每一個渲染對象都對應著DOM節點,但是非視覺(隱藏,不占位)DOM元素不會插入渲染樹,如<head>元素或聲明display: none;的元素,渲染對象與DOM節點不是簡單的一對一的關系,一個DOM可以對應一個渲染對象,但一個DOM元素也可能對應多個渲染對象,因為有很多元素不止包含一個CSS盒子,如當文本被折行時,會產生多個行盒,這些行會生成多個渲染對象;又如行內元素同時包含塊元素和行內元素,則會創建一個匿名塊級盒包含內部行內元素,此時一個DOM對應多個矩形對象(渲染對象)。

          渲染樹及其對應DOM樹如圖:

          渲染樹與對應DOM樹

          圖中渲染樹viewport即視口,是文檔的初始包含塊,scroll代表滾動區域,詳見CSS之視覺格式化模型(Visual Formatting Model)渲染樹并不會包含顯式或隱式地display:none;的標簽元素。

          布局(LAYOUT)或回流(REFLOW,RELAYOUT)

          創建渲染樹后,下一步就是布局(Layout),或者叫回流(reflow,relayout),這個過程就是通過渲染樹中渲染對象的信息,計算出每一個渲染對象的位置和尺寸,將其安置在瀏覽器窗口的正確位置,而有些時候我們會在文檔布局完成后對DOM進行修改,這時候可能需要重新進行布局,也可稱其為回流,本質上還是一個布局的過程,每一個渲染對象都有一個布局或者回流方法,實現其布局或回流。

          流(flow)

          HTML采用的是基于流的方式定位布局,其按照從左到右,從上到下的順序進行排列,詳見CSS定位機制

          全局布局與局部布局

          對渲染樹的布局可以分為全局和局部的,全局即對整個渲染樹進行重新布局,如當我們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等;而局部布局可以是對渲染樹的某部分或某一個渲染對象進行重新布局。

          臟位系統(dirty bit system)

          大多數web應用對DOM的操作都是比較頻繁,這意味著經常需要對DOM進行布局和回流,而如果僅僅是一些小改變,就觸發整個渲染樹的回流,這顯然是不好的,為了避免這種情況,瀏覽器使用了臟位系統,只有一個渲染對象改變了或者某渲染對象及其子渲染對象臟位值為”dirty”時,說明需要回流。

          表示需要布局的臟位值有兩種:

          “dirty”–自身改變,需要回流“children are dirty”–子節點改變,需要回流

          布局過程

          布局是一個從上到下,從外到內進行的遞歸過程,從根渲染對象,即對應著HTML文檔根元素<html>,然后下一級渲染對象,如對應著<body>元素,如此層層遞歸,依次計算每一個渲染對象的幾何信息(位置和尺寸)。

          幾何信息-位置和尺寸,即相對于窗口的坐標和尺寸,如根渲染對象,其坐標為(0, 0),尺寸即是視口
          尺寸(瀏覽器窗口的可視區域)。

          每一個渲染對象的布局流程基本如:

          1.計算此渲染對象的寬度(width);2.遍歷此渲染對象的所有子級,依次:2.1設置子級渲染對象的坐標2.2判斷是否需要觸發子渲染對象的布局或回流方法,計算子渲染對象的高度(height)3.設置此渲染對象的高度:根據子渲染對象的累積高,margin和padding的高度設置其高度;4.設置此渲染對象臟位值為false。

          強制回流

          在渲染樹布局完成后,再次操作文檔,改變文檔的內容或結構,或者元素定位時,會觸發回流,即需要重新布局,如請求某DOM的”offsetHeight”樣式信息等諸多情況:

          DOM操作,如增加,刪除,修改或移動;變更內容;激活偽類;訪問或改變某些CSS屬性(包括修改樣式表或元素類名或使用JavaScript操作等方式);瀏覽器窗口變化(滾動或尺寸變化)
          $('body').css('padding'); // reflow
          $('body')[0].offsetHeight; // relow
          

          有過CSS3動畫開發經驗的同學可能會有經歷,如下入場動畫:

              .slide-left {
                  -webkit-transition: margin-left 1s ease-out;
                  -moz-transition: margin-left 1s ease-out;
                  -o-transition: margin-left 1s ease-out;
                  transition: margin-left 1s ease-out;
              }
          

          然后執行如下腳本:

              var $slide = $('.slide-left');
              $slide.css({
                  "margin-left": "100px"
              }).addClass('slide-left');
              $slide.css({
                  "margin-left": "10px"
              });
          

          我們會發現并沒有效果,為什么呢?因為對margin-left的修改并沒有觸發回流,元素margin-left值的改變被緩存,如果我們在中間強制觸發回流:

              var $slide = $('.slide-left');
              $slide.css({
                  "margin-left": "100px"
              });
              console.log($slide.css('padding'));
              $slide.addClass('slide-left');
              $slide.css({
                  "margin-left": "10px"
              });
          

          再看就達到了預期效果。

          繪制(PAINTING)

          最后是繪制(paint)階段或重繪(repaint)階段,瀏覽器UI組件將遍歷渲染樹并調用渲染對象的繪制(paint)方法,將內容展現在屏幕上,也有可能在之后對DOM進行修改,需要重新繪制渲染對象,也就是重繪,繪制和重繪的關系可以參考布局和回流的關系。

          全局與局部繪制

          與布局相似,繪制也分為全局和局部繪制,即對整個渲染樹或某些渲染對象進行繪制。

          觸發重繪

          我們已經知道很多操作可能會觸發回流,那么什么時候可能觸發重繪呢,通常,當改變元素的視覺樣式,如background-color,visibility,margin,padding或字體顏色時會觸發全局或局部重繪,如:

              $('body').css('color', 'red'); // repaint
              $('body').css('margin', '2px'); // reflow, repaint
          

          頁面渲染優化

          瀏覽器對上文介紹的關鍵渲染路徑進行了很多優化,針對每一次變化產生盡量少的操作,還有優化判斷重新繪制或布局的方式等等。

          在改變文檔根元素的字體顏色等視覺性信息時,會觸發整個文檔的重繪,而改變某元素的字體顏色則只觸發特定元素的重繪;改變元素的位置信息會同時觸發此元素(可能還包括其兄弟元素或子級元素)的布局和重繪。某些重大改變,如更改文檔根元素<html>的字體尺寸,則會觸發整個文檔的重新布局和重繪,據此及上文所述,推薦以下優化和實踐:

          1.HTML文檔結構層次盡量少,最好不深于六層;2.腳本盡量后放,放在</body>前即可;3.少量首屏樣式內聯放在<head>標簽內;4.樣式結構層次盡量簡單;5.在腳本中盡量減少DOM操作,盡量緩存訪問DOM的樣式信息,避免過度觸發回流;6.減少通過JavaScript代碼修改元素樣式,盡量使用修改class名方式操作樣式或動畫;7.動畫盡量使用在絕對定位或固定定位的元素上;8.隱藏在屏幕外,或在頁面滾動時,盡量停止動畫;9.盡量緩存DOM查找,查找器盡量簡潔;10.涉及多域名的網站,可以開啟域名預解析

          實例

          當我們訪問一個頁面時,瀏覽器渲染事件詳細日志圖如下:

          瀏覽器渲染事件日志

          1.發起請求;2.解析HTML;3.解析樣式;4.執行JavaScript;5.布局;6.繪制
          參考:
          http://taligarsiel.com/Projects/howbrowserswork1.htm
          https://bitsofco.de/understanding-the-critical-rendering-path/
          原創文章,轉載請注明 轉載自 熊建剛的博客,本文鏈接地址: 淺析前端頁面渲染機制 
          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/140

          TypeScript 的 類型保護機制

          在編寫 TS 時,它做了比我們看到的更多的事情,例如類型保護機制。讓我們編寫的代碼更加嚴謹,至于怎么回事,讓我們來看看吧。由于這些機制的存在,就算你仍舊以 JS 原生的書寫方式,也能幫助你提前發現代碼中潛在的問題。

          幾道面試題來看JavaScript執行機制

          根據 JavaScript 的運行環境,鎖定它為單線程,任務需要排隊執行,如果網站資源比較大,這樣會導致瀏覽器加載會很慢,但實際上并沒有,大家肯定立刻想到了同步和異步。所謂的同步和異步也是在排隊,只是排隊的地方不同。

          從 javascript 事件循環看 Vue.nextTick 的原理和執行機制

          Vue 的特點之一就是響應式,但是有些時候數據更新了,我們看到頁面上的 DOM 并沒有立刻更新。如果我們需要在 DOM 更新之后再執行一段代碼時,可以借助 nextTick 實現。

          weakSet垃圾回收機制

          如果其它對象沒有引用該對象,垃圾回收機制會自動回收該對象所占的內存,不會考慮該對象是否還在WeakSet對象中。 正是由于以上特性,WeakSet中的成員對象會隨時消失(垃圾回收機制運行前后可能會不同)

          React Fiber的優先級調度機制與事件系統

          經典的事件系統分成兩大塊,綁定事件與分派事件,在瀏覽器中,分派事件很少人會直接dispatchEvent。因為創建一個DOM 事件是非常復雜的事情,不同的事件對象對應不同的事件構造器,傳參也五花八門 ?

          JS底層機制

          微任務:Promise,process.nextTick宏任務:整體代碼script,setTimeout,setInterval,微任務會先于宏任務執行,微任務隊列空了才去執行下一個宏任務

          js執行機制

          js是單線程的,為什么可以執行異步操作呢?這歸結與瀏覽器(js的宿主環境)通過某種方式使得js具備了異步的屬性。進程:正在運行中的應用程序。每個進程都自己獨立的內存空間。例如:打開的瀏覽器就是一個進程。

          對NodeJS模塊機制的理解

          模塊定義上下文提供exports對象用于導出當前模塊的方法和變量,并且他是唯一的導出出口,exports實際上是module.exports,而module.exports就是以一個暴露給外部的對象。

          Js事件循環(Event Loop)機制

          Event Loop是計算機系統的一種運行機制,是個很重要的概念。而Javascript用這種機制來解決單線程運行帶來的問題。理解很熟悉將會有利于我們更容易理解Vue的異步事件。Js是一種運行在網頁的簡單的腳本語言

          javaScriipt 使用垃圾回收機制來自動管理內存

          js 的回收機制目前分為兩種方式:1.標記清除(各大瀏覽器主流算法)2.引用技術這種算法的思想是跟蹤記錄所有值被引用的次數。javaScript 引擎目前都不再使用這種算法

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网