整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          瀏覽器層面優(yōu)化前端性能(2):Reader引擎線程與模塊分析優(yōu)化點

          eader 引擎線程與模塊分析


          首先是網(wǎng)頁內(nèi)容,加載完輸入到HTML解釋器,解釋后構(gòu)成DOM樹,這期間如果遇到JavaScript代碼就交給JavaScript引擎去處理,如果網(wǎng)頁中包含CSS,就交給CSS解釋器;DOM樹簡歷的時候,渲染引擎接收來自CSS解釋器的樣式信息,構(gòu)建一個新的你日不會吐模型,該模型由布局模塊計算模型內(nèi)部各個元素的位置和大小信息

          渲染流程有四個主要步驟:

          1. 解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹
          2. 構(gòu)建Render樹 - 接下來不管是內(nèi)聯(lián)式,外聯(lián)式還是嵌入式引入的CSS樣式會被解析生成CSSOM樹,根據(jù)DOM樹與CSSOM樹生成另外一棵用于渲染的樹-渲染樹(Render tree),
          3. 布局Render樹 - 然后對渲染樹的每個節(jié)點進行布局處理,確定其在屏幕上的顯示位置
          4. 繪制Render樹 - 最后遍歷渲染樹并用UI后端層將每一個節(jié)點繪制出來

          DOM樹與Render樹

          renderer與DOM元素是相對應(yīng)的,但并不是一一對應(yīng),有些DOM元素沒有對應(yīng)的renderer,而有些DOM元素卻對應(yīng)了好幾個renderer,對應(yīng)多個renderer的情況是普遍存在的,就是為了解決一個renderer描述不清楚如何顯示出來的問題,譬如有下拉列表的select元素,我們就需要三個renderer:一個用于顯示區(qū)域,一個用于下拉列表框,還有一個用于按鈕。

          另外,renderer與DOM元素的位置也可能是不一樣的。那些添加了float或者position:absolute的元素,因為它們脫離了正常的文檔流,構(gòu)造Render樹的時候會針對它們實際的位置進行構(gòu)造。

          布局與繪制

          上面確定了renderer的樣式規(guī)則后,然后就是重要的顯示元素布局了。當(dāng)renderer構(gòu)造出來并添加到Render樹上之后,它并沒有位置跟大小信息,為它確定這些信息的過程,接下來是布局(layout)。

          瀏覽器進行頁面布局基本過程是以瀏覽器可見區(qū)域為畫布,左上角為(0,0)基礎(chǔ)坐標(biāo),從左到右,從上到下從DOM的根節(jié)點開始畫首先確定顯示元素的大小跟位置,此過程是通過瀏覽器計算出來的,用戶CSS中定義的量未必就是瀏覽器實際采用的量。如果顯示元素有子元素得先去確定子元素的顯示信息

          布局階段輸出的結(jié)果稱為box盒模型(width,height,margin,padding,border,left,top,…),盒模型精確表示了每一個元素的位置和大小,并且所有相對度量單位此時都轉(zhuǎn)化為了絕對單位。

          在繪制(painting)階段,渲染引擎會遍歷Render樹,并調(diào)用renderer的 paint() 方法,將renderer的內(nèi)容顯示在屏幕上。繪制工作是使用UI后端組件完成的。

          回流與重繪

          回流(reflow)當(dāng)瀏覽器發(fā)現(xiàn)某個部分發(fā)生了點變化影響了布局,需要倒回去重新渲染。reflow 會從<html>這個 root frame 開始遞歸往下,依次計算所有的結(jié)點幾何尺寸和位置。reflow 幾乎是無法避免的。現(xiàn)在界面上流行的一些效果,比如樹狀目錄的折疊、展開(實質(zhì)上是元素的顯示與隱藏)等,都將引起瀏覽器的 reflow。鼠標(biāo)滑過、點擊……只要這些行為引起了頁面上某些元素的占位面積、定位方式、邊距等屬性的變化,都會引起它內(nèi)部、周圍甚至整個頁面的重新渲染。通常我們都無法預(yù)估瀏覽器到底會 reflow 哪一部分的代碼,它們都彼此相互影響著。


          重繪(repaint):改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內(nèi)部布局的屬性時,屏幕的一部分要重畫,但是元素的幾何尺寸沒有變。


          關(guān)鍵渲染路徑與阻塞渲染

          在瀏覽器拿到HTML、CSS、JS等外部資源到渲染出頁面的過程,有一個重要的概念關(guān)鍵渲染路徑(Critical Rendering Path)。

          例如為了保障首屏內(nèi)容的最快速顯示,通常會提到一個漸進式頁面渲染,但是為了漸進式頁面渲染,就需要做資源的拆分,那么以什么粒度拆分、要不要拆分,不同頁面、不同場景策略不同。

          現(xiàn)代瀏覽器總是并行加載資源,例如,當(dāng) HTML 解析器(HTML Parser)被腳本阻塞時,解析器雖然會停止構(gòu)建 DOM,但仍會識別該腳本后面的資源,并進行預(yù)加載。

          • CSS 被視為渲染阻塞資源(包括JS),這意味著瀏覽器將不會渲染任何已處理的內(nèi)容,直至 CSSOM 構(gòu)建完畢,才會進行下一階段。
          • 存在阻塞的 CSS 資源時,瀏覽器會延遲 JavaScript 的執(zhí)行和 DOM 構(gòu)建
            • css加載不會阻塞DOM樹的解析
            • css加載會阻塞DOM樹的渲染
            • css不會阻塞JS的加載
            • css加載會阻塞后面js語句的執(zhí)行
          • JavaScript 被認為是解釋器阻塞資源,HTML解析會被JS阻塞,它不僅可以讀取和修改 DOM 屬性,還可以讀取和修改 CSSOM 屬性。
            • 當(dāng)瀏覽器遇到一個 script 標(biāo)記時,DOM 構(gòu)建將暫停,直至腳本完成執(zhí)行。
            • JavaScript 可以查詢和修改 DOM 與 CSSOM。
            • CSSOM 構(gòu)建時,JavaScript 執(zhí)行將暫停,直至 CSSOM 就緒。
            • 沒有js的理想情況下,html與css會并行解析,分別生成DOM與CSSOM,然后合并成Render Tree,進入Rendering Pipeline;但如果有js,css加載會阻塞后面js語句的執(zhí)行,而(同步)js腳本執(zhí)行會阻塞其后的DOM解析(所以通常會把css放在頭部,js放在body尾)

          CSS 優(yōu)先:引入順序上,CSS 資源先于 JavaScript 資源。JavaScript 應(yīng)盡量少影響 DOM 的構(gòu)建。

          改變腳本加載次序defer/async/document.createElement

          defer

          defer 屬性表示延遲執(zhí)行引入 JavaScript,即 JavaScript 加載時 HTML 并未停止解析,這兩個過程是并行的。整個 document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無關(guān)),會執(zhí)行所有由 defer-script 加載的 JavaScript 代碼,再觸發(fā)DOMContentLoaded(初始的 HTML 文檔被完全加載和解析完成之后觸發(fā),無需等待樣式表圖像和子框架的完成加載) 事件。

          defer 不會改變 script 中代碼的執(zhí)行順序,示例代碼會按照 1、2、3 的順序執(zhí)行。所以,defer 與相比普通 script,有兩點區(qū)別:載入 JavaScript 文件時不阻塞 HTML 的解析,執(zhí)行階段被放到 HTML 標(biāo)簽解析完成之后。

          async

          async 屬性表示異步執(zhí)行引入的 JavaScript,與 defer 的區(qū)別在于,如果已經(jīng)加載好,就會開始執(zhí)行,無論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發(fā)(HTML解析完成事件)之后。需要注意的是,這種方式加載的 JavaScript 依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發(fā)之前或之后執(zhí)行,但一定在 load 觸發(fā)之前執(zhí)行。

          從上一段也能推出,多個 async-script 的執(zhí)行順序是不確定的,誰先加載完誰執(zhí)行。值得注意的是,向 document 動態(tài)添加 script 標(biāo)簽時,async 屬性默認是 true。

          document.createElement

          使用 document.createElement 創(chuàng)建的 script 默認是異步的

          通過動態(tài)添加 script 標(biāo)簽引入 JavaScript 文件默認是不會阻塞頁面的。如果想同步執(zhí)行,需要將 async 屬性人為設(shè)置為 false。

          優(yōu)化渲染性能

          chrome 官方文檔:https://developers.google.com/web/fundamentals/performance/?hl=en

          翻譯:https://x5.tencent.com/tbs/document/doc-chrome.html

          優(yōu)化JS的執(zhí)行效率

          動畫實現(xiàn)使用requestAnimationFrame

          setTimeout(callback)和setInterval(callback)無法保證callback函數(shù)的執(zhí)行時機,很可能在幀結(jié)束的時候執(zhí)行,從而導(dǎo)致丟幀。

          requestAnimationFrame(callback)可以保證callback函數(shù)在每幀動畫開始的時候執(zhí)行。拓展閱讀《頻率史—從電源頻率到音頻采樣頻率與視頻幀率:29.97/44.1》、《弄懂javascript的執(zhí)行機制:事件輪詢|微任務(wù)和宏任務(wù)》

          長耗時的JS代碼放到Web Workers中執(zhí)行

          JS代碼運行在瀏覽器的主線程上,與此同時,瀏覽器的主線程還負責(zé)樣式計算、布局、繪制的工作,如果JavaScript代碼運行時間過長,就會阻塞其他渲染工作,很可能會導(dǎo)致丟幀。

          前面提到每幀的渲染應(yīng)該在16ms內(nèi)完成,但在動畫過程中,由于已經(jīng)被占用了不少時間,所以JavaScript代碼運行耗時應(yīng)該控制在3-4毫秒。

          如果真的有特別耗時且不操作DOM元素的純計算工作,可以考慮放到Web Workers中執(zhí)行。

          CSS渲染與布局優(yōu)化

          添加或移除一個DOM元素、修改元素屬性和樣式類、應(yīng)用動畫效果等操作,都會引起DOM結(jié)構(gòu)的改變,從而導(dǎo)致瀏覽器要repaint或者reflow。

          降低樣式選擇器的復(fù)雜度

          • 盡量保持class的簡短,或者使用Web Components框架(如:Omi)。
          • 降低樣式選擇器的復(fù)雜度;使用基于class的方式,比如BEM(Block, Element, Modifier)。
          • 減少css嵌套,如sass使用@at-root
          • 減少需要執(zhí)行樣式計算的元素的個數(shù)
          • 對于樣式計算來說,范圍越小、規(guī)則越簡單的話,處理效率越高。
            • 在過去,如果你修改了body元素的class屬性,那么頁面里所有元素都要重新計算樣式。現(xiàn)代的瀏覽器中不再這樣做了,瀏覽器不會檢查所有受到樣式變化影響的元素。因為會對每個DOM元素維護一個獨有的樣式規(guī)則小集合,如果這個集合發(fā)生改變,才重新計算該元素的樣式。所以,樣式計算一般是直接對那些目標(biāo)元素執(zhí)行。因此我們應(yīng)該盡可能減少需要執(zhí)行樣式計算的元素的個數(shù)
            • 一般來說在最壞的情況下,樣式計算量 = 元素個數(shù) x 樣式選擇器個數(shù)因為對每個元素最少需要檢查一次所有的樣式,以確認是否
            • Web Components中的樣式計算不會跨越Shadow DOM范圍,僅在單個的Web Component中進行,而不是在整個頁面的DOM樹上進行

          避免大規(guī)模、復(fù)雜的布局

          布局,就是瀏覽器計算DOM元素的幾何信息的過程:元素大小和在頁面中的位置。每個元素都有一個顯式或隱式的大小信息,決定于其CSS屬性的設(shè)置、或是元素本身內(nèi)容的大小、抑或是其父元素的大小。在Blink/WebKit內(nèi)核的瀏覽器和IE中,這個過程稱為布局。在基于Gecko的瀏覽器(比如Firefox)中,這個過程稱為Reflow。

          盡可能避免觸發(fā)布局

          布局的時間消耗主要在于:

          • 需要布局的DOM元素的數(shù)量
          • 布局過程的復(fù)雜程度

          一份詳細的能觸發(fā)布局、繪制或渲染層合并的CSS屬性清單:CSS Triggers

          使用flexbox替代老的布局模型

          新的Flexbox比舊的Flexbox和基于浮動的布局模型更高效。

          在任何情況下,不管是是否使用Flexbox,你都應(yīng)該努力避免同時觸發(fā)所有布局,特別在頁面對性能敏感的時候(比如執(zhí)行動畫效果或頁面滾動時)。

          避免強制同步布局事件的發(fā)生

          將一幀畫面渲染到屏幕上的處理順序如下所示:

          • 在JavaScript腳本運行的時候,它能獲取到的元素樣式屬性值都是上一幀畫面的,都是舊的值。
          • 如果想在這一幀開始的時候,讀取一個元素屬性值,就需要修改當(dāng)前元素的某個屬性(可能觸發(fā)重繪與回流)。
          • 為了避免觸發(fā)不必要的布局過程,你應(yīng)該首先批量讀取元素樣式屬性,然后再對樣式屬性進行寫操作。

          大多數(shù)情況下,都不需要先修改然后再讀取元素的樣式屬性值,使用上一幀的值就足夠了。過早地同步執(zhí)行樣式計算和布局是潛在的頁面性能的瓶頸之一

          避免快速連續(xù)的布局

          比強制同步布局更糟:連續(xù)快速的多次執(zhí)行它。如:

          for (var i = 0; i < paragraphs.length; i++) {
               paragraphs[i].style.width = box.offsetWidth + 'px';
          }

          FastDom是一個輕量的庫,它提供一個公共接口,能讓DOM的讀/寫操作捆綁在一起。

          https://github.com/wilsonpage/fastdom

          簡化繪制的復(fù)雜度、減小繪制區(qū)域

          繪制并非總是在內(nèi)存中的單層畫面里完成的。實際上,瀏覽器在必要時將會把一幀畫面繪制成多層畫面,然后將這若干層畫面合并成一張圖片顯示到屏幕上。

          這種繪制方式的好處是,使用tranforms來實現(xiàn)移動效果的元素將會被正常繪制,同時不會觸發(fā)對其他元素的繪制。這種處理方式和思想跟圖像處理軟件(比如Sketch/GIMP/Photoshop)是一致的,它們都是可以在圖像中的某個單個圖層上做操作,最后合并所有圖層得到最終的圖像。

          提升移動或漸變元素的繪制層

          • 在頁面中創(chuàng)建一個新的渲染層的最好方式就是使用CSS屬性will-change,同時再與transform屬性一起使用,就會創(chuàng)建一個新的組合層:will-change: transform;
          • 對于那些目前還不支持will-change屬性、但支持創(chuàng)建渲染層的瀏覽器,以使用一個3D transform屬性來強制瀏覽器創(chuàng)建一個新的渲染層:transform: translateZ(0);

          減少繪制區(qū)域

          有時候盡管把元素提升到了一個單獨的渲染層,渲染工作依然是必須的。渲染過程中一個比較有挑戰(zhàn)的問題是,瀏覽器會把兩個相鄰區(qū)域的渲染任務(wù)合并在一起進行,這將導(dǎo)致整個屏幕區(qū)域都會被繪制。比如,你的頁面頂部有一個固定位置的header,而此時屏幕底部有某個區(qū)域正在發(fā)生繪制的話,整個屏幕都將會被繪制。

          注意:在DPI較高的屏幕上,固定定位的元素會自動地被提升到一個它自有的渲染層中。但在DPI較低的設(shè)備上卻并非如此,因為這個渲染層的提升會使得字體渲染方式由子像素變?yōu)榛译A(詳細內(nèi)容請參考:Text Rendering),我們需要手動實現(xiàn)渲染層的提升。

          減少繪制區(qū)域通常需要對動畫效果進行精密設(shè)計,以保證各自的繪制區(qū)域之間不會有太多重疊,或者想辦法避免對頁面中某些區(qū)域執(zhí)行動畫效果。

          簡化繪制的復(fù)雜度

          比如js 獲取元素的offsetTop ffsetTop 比如getBoundingClientRect 消耗更少。

          在css里面,重繪 backgroun 比如 box-shadow 消耗更好。

          那些能性能更加耗資源,我也不知道,道友若知,請留言賜教,多謝。手工就 paint profiler 分析對比咯

          優(yōu)先使用渲染層合并屬性、控制層數(shù)量

          • 只使用transform/opacity來實現(xiàn)動畫效果
          • 應(yīng)用了transforms/opacity屬性的元素必須獨占一個渲染層。為了對這個元素創(chuàng)建一個自有的渲染層,你必須提升該元素。在合成層上面的元素,也會合并到此圖層中。
          • 用will-change/translateZ屬性把動畫元素提升到單獨的渲染層中
          • 避免濫用渲染層提升:更多的渲染層需要更多的內(nèi)存和更復(fù)雜的管理
          • 過多的渲染層來帶的開銷而對頁面渲染性能產(chǎn)生的影響,甚至遠遠超過了它在性能改善上帶來的好處。由于每個渲染層的紋理都需要上傳到GPU處理,因此我們還需要考慮CPU和GPU之間的帶寬問題、以及有多大內(nèi)存供GPU處理這些紋理的問題。

          從性能方面考慮,最理想的渲染流水線是沒有布局和繪制環(huán)節(jié)的,只需要做渲染層的合并即可:

          之前也參看:《關(guān)于css3之transform一些坑的總結(jié)-transform對普通元素的N多渲染》

          對用戶輸入事件的處理去抖動

          • 避免使用運行時間過長的輸入事件處理函數(shù),它們會阻塞頁面的滾動
          • 避免在輸入事件處理函數(shù)中修改樣式屬性
          • 對輸入事件處理函數(shù)去抖動,存儲事件對象的值,然后在requestAnimationFrame 回調(diào)函數(shù)中修改樣式屬性

          具體參看《Debounce 和 Throttle 的原理及實現(xiàn)》


          參考文章:

          從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理 https://www.cnblogs.com/cangqinglang/p/8963557.html

          Chrome源碼剖析、上--多線程模型、進程通信、進程模型https://www.cnblogs.com/v-July-v/archive/2011/04/02/2036008.html

          Chrome源代碼分析之進程和線程模型(三) https://blog.csdn.net/namelcx/article/details/6582730

          http://dev.chromium.org/developers/design-documents/multi-process-architecture

          chrome渲染機制淺析 https://www.jianshu.com/p/99e450fc04a5

          淺析瀏覽器渲染原理 https://segmentfault.com/a/1190000012960187

          javascript宏任務(wù)和微任務(wù) https://www.cnblogs.com/fangdongdemao/p/10262209.html

          瀏覽器與Node的事件循環(huán)(Event Loop)有何區(qū)別? https://blog.csdn.net/Fundebug/article/details/86487117





          轉(zhuǎn)載本站文章《瀏覽器層面優(yōu)化前端性能(2):Reader引擎線程與模塊分析優(yōu)化點》,
          請注明出處:https://www.zhoulujun.cn/html/webfront/browser/webkit/2020_0615_8464.html

          在面試的時候,經(jīng)常會遇到一道經(jīng)典的面試題:

          如何優(yōu)化網(wǎng)頁加載速度?

          常規(guī)的回答中總會有一條:

          把 css 文件放在頁面頂部,把 js 文件放在頁面底部。

          那么,為什么要把 js 文件放在頁面的最底部呢?

          我們先來看下這段代碼:

          <!DOCTYPE html>
          <html lang="zh">
            <head>
              <title>Hi</title>
              <script>
                  console.log("Howdy ~");
              </script>
              <script src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script>
              <script src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script>
            </head>
            <body>
              Hello  ~
            </body>
          </html>

          他的執(zhí)行順序是:

          • 在控制臺打印:Howdy ~
          • 請求并執(zhí)行 vue.global.js
          • 請求并執(zhí)行 vue-router.global.js
          • 在頁面中展示:Hello ~
          • 觸發(fā) DOMContentLoaded 事件

          script 加載邏輯

          瀏覽器的解析規(guī)則是:如果遇到 script 標(biāo)簽,則暫停構(gòu)建 DOM,轉(zhuǎn)而開始執(zhí)行 script 標(biāo)簽,如果是外部 script,那么瀏覽器還需要一直等待其「下載」并「執(zhí)行」后,再繼續(xù)解析后面的 HTML。

          如果請求并執(zhí)行「vue.global.js」需要 3 秒,「vue-router.global.js」需要 2 秒,那么頁面中的 Hello ~,則至少需要 5 秒以上才會展示出來。

          可以看到,script 標(biāo)簽會阻塞瀏覽器解析 HTML,如果把 script 都放在 head 中,在網(wǎng)絡(luò)不佳的情況下,就會導(dǎo)致頁面長期處于白屏狀態(tài)。

          在很久以前,一般都是將這些外聯(lián)腳本,放在 body 標(biāo)簽的最后面,確保先解析展示 body 中的內(nèi)容,然后再一個個請求執(zhí)行這些外聯(lián)腳本。

          那有沒有其他更優(yōu)雅的解決方案呢?

          答案是肯定的,現(xiàn)在 script 標(biāo)簽新增了 2 個屬性:deferasync,就是為了解決此類問題,提升頁面性能的。

          <script defer>

          先看一下 MDN 上的解釋:

          這個布爾屬性被設(shè)定用來通知瀏覽器該腳本將在文檔完成解析后,觸發(fā) DOMContentLoaded 事件前執(zhí)行。

          有 defer 屬性的腳本會阻止 DOMContentLoaded 事件,直到腳本被加載并且解析完成。

          文檔是直接總結(jié)了他的特性,我們先看看下面的代碼,展開說說細節(jié),加深一下理解。

          <!DOCTYPE html>
          <html lang="zh">
            <head>
              <title>Hi</title>
              <script>
                console.log("Howdy ~");
              </script>
              <script defer src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script>
              <script defer src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script>
            </head>
            <body>
              Hello  ~
            </body>
          </html>

          他的執(zhí)行順序是:

          • 在控制臺打印:Howdy ~
          • 在頁面中展示:Hello ~
          • 請求并執(zhí)行 vue.global.js
          • 請求并執(zhí)行 vue-router.global.js
          • 觸發(fā) DOMContentLoaded 事件

          script defer 加載邏輯

          如果在 script 標(biāo)簽上設(shè)置了 defer 屬性,那么在瀏覽器解析到這里時,會默默的在后臺開始下載此腳本,并繼續(xù)解析后面的 HTML,并不會阻塞解析操作。

          等到 HTML 解析完成之后,瀏覽器會立即執(zhí)行后臺下載的腳本,腳本執(zhí)行完成之后,才會觸發(fā) DOMContentLoaded 事件。

          看起來還是蠻好理解的吧?咱們再來討論 2 個小細節(jié):

          Q1: 如果 HTML 解析完成之后,設(shè)置了 defer 屬性的腳本還沒下載完成,會怎樣?

          A1: 瀏覽器會等腳本下載完成之后,再執(zhí)行此腳本,執(zhí)行完成之后,再觸發(fā) DOMContentLoaded 事件。

          Q2: 如果有多個設(shè)置了 defer 屬性的腳本,那瀏覽器會如何處理?

          A2: 瀏覽器會并行的在后臺下載這些腳本,等 HTML 解析完成,并且所有腳本下載完成之后,再按照他們在 HTML 中出現(xiàn)的相對順序執(zhí)行,等所有腳本執(zhí)行完成之后,再觸發(fā) DOMContentLoaded 事件。

          最佳實踐:

          建議所有的外聯(lián)腳本都默認設(shè)置此屬性,因為他不會阻塞 HTML 解析,可以并行下載 JavaScript 資源,還可以按照他們在 HTML 中的相對順序執(zhí)行,確保有依賴關(guān)系的腳本運行時,不會缺少依賴。

          在 SPA 的應(yīng)用中,可以考慮把所有的 script 標(biāo)簽加上 defer 屬性,并且放到 body 的最后面。在現(xiàn)代瀏覽器中,可以并行下載提升速度,也可以確保在老瀏覽器中,不阻塞瀏覽器解析 HTML,起到降級的作用。

          注意:

          • defer 屬性僅適用于外部腳本,如果 script 腳本沒有 src,則會忽略 defer 特性。
          • defer 屬性對模塊腳本(script type='module')無效,因為模塊腳本就是以 defer 的形式加載的。

          <script async>

          按照慣例,先看一下 MDN 上的解釋:

          對于普通腳本,如果存在 async 屬性,那么普通腳本會被并行請求,并盡快解析和執(zhí)行。

          對于模塊腳本,如果存在 async 屬性,那么腳本及其所有依賴都會在延緩隊列中執(zhí)行,因此它們會被并行請求,并盡快解析和執(zhí)行。


          該屬性能夠消除解析阻塞的 Javascript。

          解析阻塞的 Javascript 會導(dǎo)致瀏覽器必須加載并且執(zhí)行腳本,之后才能繼續(xù)解析。

          感覺這段描述的已經(jīng)蠻清晰了,不過咱們還是先看看下面的代碼,展開說說細節(jié),加深一下理解。

          <!DOCTYPE html>
          <html lang="zh">
            <head>
              <title>Hi</title>
              <script>
                console.log("Howdy ~");
              </script>
              <script async src="https://google-analytics.com/analytics.js"></script>
              <script async src="https://ads.google.cn/ad.js"></script>
            </head>
            <body>
              Hello  ~
            </body>
          </html>

          他的執(zhí)行順序是:

          • 在控制臺打印:Howdy ~
          • 并行請求 analytics.jsad.js
          • 在頁面中展示:Hello ~
          • 根據(jù)網(wǎng)絡(luò)的實際情況,以下幾項會無序執(zhí)行 執(zhí)行 analytics.js(下載完后,立即執(zhí)行)執(zhí)行 ad.js(下載完后,立即執(zhí)行)觸發(fā) DOMContentLoaded 事件(可能在在上面 2 個腳本之前,之間,之后觸發(fā))

          script async 加載邏輯

          瀏覽器在解析到帶有 async 屬性的 script 標(biāo)簽時,也不會阻塞頁面,同樣是在后臺默默下載此腳本。當(dāng)他下載完后,瀏覽器會暫停解析 HTML,立馬執(zhí)行此腳本。

          看起來還是蠻好理解的吧?咱們再來討論 2 個小細節(jié):

          Q1:如果設(shè)置了 async 屬性的 script 下載完之后,瀏覽器還沒解析完 HTML,會怎樣?

          A1:瀏覽器會暫停解析 HTML,立馬執(zhí)行此腳本,等執(zhí)行完之后,再繼續(xù)解析 HTML。

          Q2:如果有多個 async 屬性的 script 標(biāo)簽,那等他們下載完成之后,會按照代碼順序執(zhí)行嗎?

          A2:不會。執(zhí)行順序是:誰先下載完成,誰先執(zhí)行。async 的特點是「完全獨立」,不依賴其他內(nèi)容。

          最佳實踐:

          當(dāng)我們的項目,需要集成其他獨立的第三方庫時,可以使用此屬性,他們不依賴我們,我們也不依賴于他們。 通過設(shè)置此屬性,讓瀏覽器異步下載并執(zhí)行他,是個不錯的優(yōu)化方案。

          注意:

          • async 特性僅適用于外部腳本,如果 script 腳本沒有 src,則會忽略 async 特性。

          總結(jié)

          defer

          • 不阻塞瀏覽器解析 HTML,等解析完 HTML 之后,才會執(zhí)行 script
          • 會并行下載 JavaScript 資源。
          • 會按照 HTML 中的相對順序執(zhí)行腳本。
          • 會在腳本下載并執(zhí)行完成之后,才會觸發(fā) DOMContentLoaded 事件。
          • 在腳本執(zhí)行過程中,一定可以獲取到 HTML 中已有的元素。
          • defer 屬性對模塊腳本無效。
          • 適用于:所有外部腳本(通過 src 引用的 script)。

          async

          • 不阻塞瀏覽器解析 HTML,但是 script 下載完成后,會立即中斷瀏覽器解析 HTML,并執(zhí)行此 script
          • 會并行下載 JavaScript 資源。
          • 互相獨立,誰先下載完,誰先執(zhí)行,沒有固定的先后順序,不可控。
          • 由于沒有確定的執(zhí)行時機,所以在腳本里面可能會獲取不到 HTML 中已有的元素。
          • DOMContentLoaded 事件和 script 腳本無相關(guān)性,無法確定他們的先后順序。
          • 適用于:獨立的第三方腳本。

          另外:asyncdefer 之間最大的區(qū)別在于它們的執(zhí)行時機。

          One More Thing

          你有沒有想過,如果一個 script 標(biāo)簽同時設(shè)置 deferasync,瀏覽器會如何處理?

          先說結(jié)論:從表現(xiàn)形式上來說,async 的優(yōu)先級比 defer 高,也就是如果同時存在這 2 個屬性,那么瀏覽器將會以 async 的特性去加載此腳本。

          這主要分 2 種情況: 如果是「普通腳本」,瀏覽器會優(yōu)先判斷async屬性是否存在,如果存在,則以async特性去加載此腳本,如果不存在,再去判斷是否存在defer屬性。

          如果是「模塊腳本」,瀏覽器會判斷async屬性是否存在:

          • 如果存在,瀏覽器會并行下載此模塊和他的所有依賴模塊,等全部下載完成之后,會立刻執(zhí)行此腳本。
          • 如果不存在,瀏覽器也會并行下載此模塊和他的所有依賴模塊,然后等瀏覽器解析完 HTML 之后,再執(zhí)行此腳本。
          • 另外需要注意的是:在模塊腳本上設(shè)置 defer 屬性是無效的。

          一圖勝千言

          最后,用一張圖概括一下這兩個屬性的加載模式吧:

          defer 和 async 的加載模式

          思考題

          • 為什么瀏覽器在解析到普通的 script 標(biāo)簽時,必須先執(zhí)行他?
          • 普通的 script 標(biāo)簽會阻塞瀏覽器解析 HTML,這會導(dǎo)致什么問題?

          本文首發(fā)于:https://github.com/mrlmx/blogs/issues/4 ,如果喜歡,記得去點個贊哦~ ??

          參考

          • https://javascript.info/script-async-defer
          • https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html
          • https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script
          • https://html.spec.whatwg.org/multipage/scripting.html

          站的加載速度是跳失率的關(guān)鍵因素,大多數(shù)買家對于網(wǎng)頁加載速度緩慢的網(wǎng)站會感到不耐煩。要了解網(wǎng)站的速度并提高網(wǎng)頁加載速度,你可以使用Google Page Speed Insights工具。

          本文將為你介紹該工具,了解它如何能夠幫助你優(yōu)化網(wǎng)站。

          Google PageSpeed Insights是什么?

          Google Page Speed Insight(PSI)是一款旨在優(yōu)化所有設(shè)備上的網(wǎng)頁、提高網(wǎng)頁加載速度的工具。

          它可以為以下各項創(chuàng)建單獨的診斷:

          1、桌面設(shè)備

          2、移動設(shè)備

          該工具會對網(wǎng)頁進行診斷,并提供頁面總體速度的分?jǐn)?shù),然后該工具會告訴你哪些地方需要優(yōu)化。

          此外,該工具會對你的網(wǎng)頁在0到100之間評定一個分?jǐn)?shù),使你確切地知道網(wǎng)頁的運行狀況。

          以下是使用該工具提高網(wǎng)頁加載速度的方法。

          如何解決問題?

          將網(wǎng)站的URL粘貼到Page Speed Insights并獲得分析結(jié)果后,你需要查看紅色和黃色警報,以及導(dǎo)致網(wǎng)站運行緩慢的原因。

          下面是一些使你的網(wǎng)站運行緩慢的常見問題,以及如何修復(fù)這些問題的方法。

          常見的問題及其解決方法

          1、圖像尺寸

          每個網(wǎng)站都含有一定數(shù)量的圖像,以使網(wǎng)站更加人性化、更具吸引力或更加生動有趣。但是,圖像通常可能是導(dǎo)致網(wǎng)站運行以及加載時間緩慢的原因。

          如果Page Speed Insight提醒你有關(guān)圖像尺寸的信息,你則需要執(zhí)行以下操作:

          · 查看上傳到網(wǎng)站上的圖像;

          · 使用壓縮工具或插件調(diào)整圖像大小;

          · 壓縮要上傳的圖像,以避免再次獲得低分。

          圖像經(jīng)過壓縮后,你會注意到得分和加載速度有了顯著改善。

          此外,考慮使用延遲加載圖像,在實際需要顯示圖像的時候再進行加載,這也可以幫助你提高網(wǎng)頁速度。

          2、瀏覽器緩存

          每次瀏覽器加載網(wǎng)頁時,下載網(wǎng)頁文件的過程都會在后臺進行。瀏覽器需要下載正確顯示網(wǎng)頁所需的所有文件:

          · HTML;

          · 圖片;

          · CSS;

          · JavaScript。

          此過程會使整個過程變慢,并拉低分?jǐn)?shù)。

          但是,瀏覽器緩存可以幫助你減少或消除此問題:

          · 它將文件存儲在用戶的瀏覽器中;

          · 用戶重新進入網(wǎng)站時就無需再次下載此類文件;

          · 網(wǎng)站加載速度更快。

          你可以使用緩存插件以優(yōu)化網(wǎng)站、提高加載速度。

          3、停止加載CSS和JavaScript資源

          JavaScript文件也可能會拖慢網(wǎng)頁加載速度,這是一個不容忽視的常見問題。

          要阻止這種情況的發(fā)生,你需要先停止加載CSS和JavaScript資源。

          方法如下:

          · 內(nèi)聯(lián)腳本;

          · 延遲加載不重要的腳本

          這樣,你可以確保提高加載速度,并顯著降低跳失率。

          4、精簡代碼

          Page Speed Insight工具可能會警告你的另一個問題是源代碼占用的空間。

          編碼會占用一定的空間,如果能對其進行精簡,則可以加快網(wǎng)頁加載時間。

          你可以通過刪除或修復(fù)重復(fù)數(shù)據(jù)來進行精簡,例如:

          · 空白空間;

          · 未使用的代碼;

          · 重復(fù)格式化;

          · 評論;

          · 等等。

          需要精簡的三個資源是:

          · JS;

          · CSS;

          · HTML。

          每個資源都有一個工具或插件,可以幫助你進行精簡。確保使用它們,并盡快解決此問題。

          5、重定向

          此外,重定向過程也會對加載速度造成影響。頁面加載時間要是過長,很可能是重定向?qū)е缕錈o法正常運轉(zhuǎn)的原因。

          當(dāng)頁面重定向到針對某些設(shè)備優(yōu)化過的不同頁面時,就會發(fā)生這種情況。

          那么,如何解決這個問題?

          有一種方法:確保構(gòu)建適合所有設(shè)備的響應(yīng)式設(shè)計。這樣,你就無需重定向,并且能夠提高網(wǎng)頁的加載速度。

          【限時免費】如何使用谷歌官方工具,解決營銷路上遇到的道道難關(guān)?

          (編譯/雨果網(wǎng) 陳杰)

          【特別聲明】未經(jīng)許可同意,任何個人或組織不得復(fù)制、轉(zhuǎn)載、或以其他方式使用本網(wǎng)站內(nèi)容。轉(zhuǎn)載請聯(lián)系:editor@cifnews.com

          上雨果網(wǎng)搜索“跨境資料庫”,領(lǐng)取歐美/東南亞各國市場商機、各大平臺熱銷品報告、跨境電商營銷白皮書!


          主站蜘蛛池模板: 国产成人午夜精品一区二区三区| 一区二区中文字幕| 日韩十八禁一区二区久久| 国产色精品vr一区区三区| 亚洲大尺度无码无码专线一区| 波多野结衣一区二区免费视频| 国产精品熟女视频一区二区| 爆乳熟妇一区二区三区| 亚洲AV乱码一区二区三区林ゆな| 日韩精品一区二区三区四区| 嫩B人妻精品一区二区三区| 影音先锋中文无码一区| 国产综合无码一区二区三区| 一区二区不卡久久精品| 久久久无码精品人妻一区| 亚洲sm另类一区二区三区| 日韩精品无码一区二区视频| 无码av中文一区二区三区桃花岛| 亚洲男人的天堂一区二区| 日本一区二区三区不卡视频中文字幕| 杨幂AV污网站在线一区二区| 精品女同一区二区三区在线| 怡红院一区二区在线观看| 亚洲国产福利精品一区二区| 国产成人一区二区三区电影网站| 中文字幕一区二区三区有限公司 | 国产一区二区免费在线| 波霸影院一区二区| 亚洲国产精品第一区二区| 国产精品伦一区二区三级视频| 久久一区二区免费播放| 久久综合九九亚洲一区| 乱子伦一区二区三区| 麻豆一区二区99久久久久| 亚洲日本久久一区二区va| 国产精品无码一区二区三区不卡 | 亚洲国产福利精品一区二区| 国产一区二区三区四| 日本一区视频在线播放| 国产精品视频分类一区| 日韩精品一区二区三区中文字幕 |