整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          script 的三種加載模式:默認加載、defer、async

          家好,我是前端西瓜哥。今天我們來了解一下 script 腳本的三種加載方式。

          默認加載

          一般的 script 寫法為:

          <script src="app.js"></script>
          

          這種寫法有一個問題:它會 阻塞 HTML 的 DOM 構建

          假如我們在 head 元素中使用了 script 腳本,它就會阻止后面元素的渲染,包括 body 元素,此時執行document.querySeletor('body') 拿到的是 null。

          <!DOCTYPE html>
          <html>
          <head>
            <meta charset="UTF-8">
            <title>Document</title>
            <script>
              // 拿到 null
             console.log(document.querySeletor('body'));
            </script>
          </head>
          <body></body>
          </html>
          

          此外,當腳本足夠大,加載執行足夠久時,會導致頁面長時間沒能渲染出完整頁面。

          這也是我們將業務代碼腳本放到 body 最下邊的原因,這樣能確保腳本能夠訪問一個完整的 DOM 樹,也不會阻止頁面的渲染。

          缺點是,HTML 很長的時候,解析到腳本就會花上一點時間,然后才會請求對應的腳本資源。

          不過通常來說,HTML 內容都比較簡單,二者感受不到太大區別,除非你網很卡。

          defer 加載

          <script defer src="app.js"></script>
          

          defer,“延遲” 之意。這里的延遲,指的是延遲執行腳本,下載則不會被阻塞。

          需要注意的是, defer 屬性對內嵌腳本無效。畢竟腳本內容就在 HTML 里了,完全不需要請求資源了好吧。

          給 script 標簽添加了 defer 屬性后,腳本不會阻塞 DOM 樹的構建,會先下載資源,然后等待到在 DOMContentLoaded 事件前執行。

          DOMContentLoaded 事件的觸發時機為初始 HTML 被構建完成時,此時 CSS、圖片等資源不需要加載完,但我們的腳本要執行完。

          如果多個 script 設置了 defer 屬性,這幾個 script 的執行順序和聲明順序相同,即最前面的腳本先執行。并不是誰先下載誰先執行。

          實際開發中,我們可以將業務代碼腳本加上 defer 屬性,放到更上層的 head 標簽下。

          這也是最新版 HtmlWebpackPlugin 插件的默認引入打包腳本的方式。

          async 加載

          <script async src="app.js"></script>
          

          async,“異步” 之意。同樣對內嵌腳本無效。

          設置 async 后,腳本一旦被下載好了就會執行,不管什么時機。

          適合與執行順序無關的腳本,比如廣告、網站流量分析腳本。

          比如插入 Google 分析腳本:

          <script async src="//www.google-analytics.com/analytics.js"></script>
          

          動態加載

          還有一種用腳本加載腳本的特殊情況,這里也說一說。

          <script>
            const script = document.createElement('script');
            script.src = 'app-a.js';
            document.body.appendChild(script);
          </script>
          

          腳本里創建一個 script 元素,設置好 src,然后加載到 DOM 樹上,接著腳本就會下載和執行了。

          創建的 script 元素默認會給 async 設置為 true,即一旦下載好就立即執行。

          如果你要加載有依賴關系的多個腳本,就需要將 async 設置為 false。

          <script>
            const script = document.createElement('script');
            // 取消 async 加載方式
            script.async = false;
            script.src = 'app-a.js';
            document.body.appendChild(script);
          
            const script2 = document.createElement('script');
            script2.async = false;
            script2.src = 'app-b.js';
            document.body.appendChild(script2);
          </script>
          <script>console.log('我還是所有腳本中最先執行的')</script>
          

          這樣寫,就能保證先執行 app-a.js,再執行 app-b.js

          它無法做到比 HTML 中其他非動態加載的 script 腳本更早執行,這點需要注意。

          結尾

          script 有三種常見加載模式:

          • 默認加載:會阻塞 DOM 構建
          • defer 加載:下載照舊,但執行延后
          • async:下載完就立即執行,適合沒有依賴的腳本

          此外還有動態加載的腳本的情況,這種腳本默認為 async 加載形式,可通過將 async 屬性設置為 false 來解除,讓腳本順序執行。

          我是前端西瓜哥,歡迎關注我。


          前端代碼離不開瀏覽器環境,理解 js、css 代碼如何在瀏覽器中工作是非常重要的。

          如何優化渲染過程中的回流,重繪?script 腳本在頁面中是怎么個加載順序?了解這些對前端性能優化起著非常大的作用。

          借著這篇文章,讓自己對這塊知識的理解更深一步。

          渲染

          渲染樹(Render Tree)

          瀏覽器通過解析 HTML 和 CSS 后,形成對應的 DOM 樹和 CSSOM 樹。

          從根節點開始解析 DOM 樹節點并匹配對應的 CSSOM 樣式規則,選擇可見的的節點,最終結合成一顆渲染樹

          從上圖能看到渲染樹的特點:

          • 渲染樹中不包含 head、script、link、meta 之類不可見的節點
          • CSS 定義的樣式規則將和實際的 DOM 匹配,并且被 display:none 修飾的節點最終不會出現在渲染樹中

          渲染階段

          根據上圖,整個渲染階段分為三部分:

          • 渲染樹的形成:通過 DOM 和 CSSOM 形成渲染樹
          • 布局 Layout(自動重排 Reflow):基于頁面的流式布局,遍歷渲染樹節點,不斷計算節點最終的位置,幾何信息,樣式等屬性后,輸出一個“盒模型”
          • 繪制 Paint(柵格化):將節點位置,大小根據屏幕的窗口大小換算成真實的像素,同顏色等屬性一同“畫到”頁面上

          回流和重繪

          基本概念

          • 回流 Reflow:某些元素位置、幾何形狀的更改需要瀏覽器重新計算相關元素。
          • 重繪 Repaint:將回流重排好的元素繪制到頁面上,但也因某些 js、css 的修改導致渲染樹發生變化,瀏覽器需要再次繪制頁面。

          兩者的關系:觸發回流一定會觸發重繪, 而觸發重繪卻不一定會觸發回流

          下圖很形象的展示了 Mozilla 頁面的渲染過程。

          觸發回流條件

          • 首次布局渲染頁面
          • 改變瀏覽器窗口大小
          • 改變字體
          • 網頁內容變化
          • 觸發 CSS 偽類
          • 操作 DOM
          • style 樣式表發生變化
          • 調用 DOM 元素的 offsetXX, clientXX,scrollXX,getClientRects 等屬性方法,獲取元素當前的位置度量信息(參見)

          如何測試網頁性能

          都知道頻繁的渲染過程會影響網頁性能,但怎么知道網頁開始渲染內容了呢?

          我們可以通過 Chrome 的 F12,選擇 Rendering 來查看網頁的性能。

          • Paint flashing: 以綠色高亮重繪區域
          • Layout Shift Regions: 以藍色高亮布局發生變化的區域

          結合上面的方法,用 一個簡單的 Demo 來示意:

          能從圖中看到,這些操作 觸發了瀏覽器的重繪

          • 鼠標移至按鈕上,觸發了默認的 hover 效果(出現綠框)
          • 改變元素 color 屬性(出現綠框)
          • 修改元素 top 屬性,不斷改變元素位置影響布局(出現綠框,藍框)

          提升渲染性能

          布局/回流繪制/重繪 是頁面渲染必須會經過的兩個過程,不斷觸發它們肯定會增加性能的消耗。

          瀏覽器會對這些操作做優化(把它們放到一個隊列,批量進行操作),但如果我們調用上面提到的 offsetXX, clientXX,scrollXX,getClientRects 等屬性方法就會強制刷新這個隊列,導致這些隊列批量優化無效。

          下面列舉一些簡單優化方式:

          • 不要使用 table 布局 table 布局會破壞 HTML 流式解析過程,甚至內部元素改動會觸發整個 table 重繪
          • 將需更改的 class 放到最里層 明確元素位置,減少父類元素不必要渲染判斷
          • 使用 fixed、absolute 屬性修飾復雜多變的處理(動畫) 將改變范圍降到最低程度,避免影響到父級元素
          • 合并,減少 DOM 操作;通過虛擬 DOM 來代替

          腳本的加載

          link 和 script 加載文件的差異

          注:均放在 head 標簽內。

          考個問題:CSS 定義在 head 中,其需加載 5 秒,請問頁面加載后內容會先優先展示嗎?

          <!DOCTYPE html>
          <html lang="en">
            <head>
              <!-- 延遲5秒 -->
              <link rel="stylesheet" href="/css/demo.css?t=5000" />
            </head>
            <body>
              <div class="layout">我被渲染出來了</div>
            </body>
          </html>
          

          我原先以為頁面內容會優先渲染,CSS 加載完成后才改變內容樣式。其實這是錯的。

          從上圖看到,頁面加載后,body 內元素就已經解析好了,只是沒有渲染到頁面上。隨后 CSS 文件加載后,帶有樣色的內容才被渲染到頁面上。

          延遲的 link 的加載阻斷了頁面渲染,但并沒有影響 HTML 的解析,當 CSS 加載后,DOM 完成解析,CSSOM 和 DOM 形成渲染樹,最后將內容渲染到頁面上。

          反問,將 link 替換成 script 效果也一樣嗎?

          與 link 不同,script 的加載會阻斷頁面 HTML 的解析,瀏覽器解析完 script 后,會等待 js 文件加載完后,頁面才開始后續的解析,body 內容才出現。

          head 和 body 中的 script 標簽

          學前端時相信都聽過這樣的名言:

          CSS 寫在 head 里,js 寫在 body 結束標簽前

          知道了上面 link 和 script 的區別后,應該明白前半句的含義,下面來解釋下后半句。

          下面 script 均在 body 中

          頁面渲染 和 script 加載

          先看下腳本在 body 中的一般情況:

          在 body 內部的首位分別加載兩個 js 文件,前者延遲 3 秒,后者延遲 5 秒,為了清楚他們的“工作”情況,在 head 中添加了定時器示意。

          <html lang="en">
            <head>
              <script>
                var t = 0;
                var timer = setInterval(function () {
                  t++;
                  console.log('已加載 ', t, ' 秒');
                  if (t == 10) {
                    clearInterval(timer);
                  }
                }, 1 * 1000);
              </script>
            </head>
            <body>
              <script>
                var foo = 0;
                console.log('init foo', foo);
              </script>
              <script src="/js/addTen.js?t=3000"></script>
              <div>我被渲染了</div>
              <script src="/js/addOne.js?t=5000"></script>
            </body>
          </html>
          

          能看到 body 中定義的內聯腳本首先工作,初始化 foo 變量。

          隨后加載 addTen.js,并阻斷頁面渲染。3 秒后,輸出 js 內容(foo 賦值為 10),頁面并重新開始解析,展示 div 內容。

          最后加載 addOne.js ,繼續等待 2 秒后,輸出 js 內容(foo 賦值為 11)。

          多個 script 文件的加載

          如果前一個 js 文件加載慢于后一個,會有怎么個效果?

          <script src="/js/addTen.js?t=5000"></script>
          <div>我被渲染了</div>
          <script src="/js/addOne.js?t=1000"></script>
          

          兩個 script 標簽并行加載,1 秒后 addOne.js 首先加載完畢,等待 4s 秒后,addTen.js 加載完后,頁面直接渲染(因為 script 已經全部完成)。

          簡單總結下

          1. 無論在 head 還是 body 中,瀏覽器會等待 script 文件的加載(阻斷頁面解析渲染)
          2. 多個 script 的文件加載是異步的,不存在互相影響(后一個文件不需要等待前一個加載完后才下載),執行順序同定義順序

          所以建議 script 放在 body 結束標簽之前,確保頁面內容全部解析完成并開始渲染。

          DOM 的 DOMContentLoaded 事件

          DOMContentLoaded 事件可以來確定整個 DOM 是否全部加載完成,下面我們簡單測試下:

          <script>
            document.addEventListener('DOMContentLoaded', function () {
              console.log('[ready] document');
            });
          </script>
          <!-- ... -->
          <script src="/js/addTen.js?t=5000"></script>
          <div>我被渲染了</div>
          <script src="/js/addOne.js?t=1000"></script>
          

          最終輸出:

          addTen.js
          foo 10
          addOne.js
          foo 11
          [ready] document
          

          DOMContentLoaded 事件的定義是異步回調方式,當 DOM 加載完成后觸發,即使寫在最前面,也會等待后面的 script 加載完成后才觸發。

          這里順便提個 window.onload

          window.onloadDOMContentLoaded 不同,前者會等待頁面中所有的資源加載完畢后再調用執行(比如:img 標簽),后者在 DOM 加載完畢后即觸發。

          “真正的異步腳本”——動態腳本

          能看到無論 script 放在那個位置,瀏覽器都會等待他們直至 body 內的文件全部加載完。

          那有什么 真正的異步 腳本加載嗎?(不會阻斷頁面解析)

          那就是 動態腳本

          如果你接觸過第三方網頁統計腳本,那將比較了解,下面給段示例代碼:

          <script>
            document.addEventListener('DOMContentLoaded', function () {
              console.log('[ready] document');
            });
          </script>
          <script>
            var newScript = document.createElement('script');
            newScript.type = 'text/javascript';
            newScript.src = '/js/dynamicScript.js?t=8000';
            document.getElementsByTagName('head')[0].appendChild(newScript);
            // 腳本加載完畢
            if (newScript.readyState) {
              newScript.onreadystatechange = function () {
                if (newScript.readyState == 'loaded' || newScript.readyState == 'complete') {
                  console.log('dynamicScript.js loaded');
                }
              };
            } else {
              newScript.onload = function () {
                console.log('dynamicScript.js loaded');
              };
            }
          </script>
          <script src="/js/addTen.js?t=5000"></script>
          <div>我被渲染了</div>
          <script src="/js/addOne.js?t=1000"></script>
          

          最終輸出:

          addTen.js
          afoo 10
          addOne.js
          foo 11
          [ready] document
          已加載  5  秒
          已加載  6  秒
          已加載  7  秒
          已加載  8  秒
          dynamicScript.js is running
          dynamicScript.js loaded
          已加載  9  秒
          已加載  10  秒
          

          定義了需要加載 8 秒的 dynamicScript.js 文件,所有的 script 加載方式依舊異步,但 dynamicScript.js 在 DOMContentLoaded 觸發后,最后才執行,瀏覽器并沒有等待它的加載完成后才渲染頁面。

          我們也可以將它放在 head 中。這種通過腳本來動態修改 DOM 結構的加載方式是 無阻塞式 的,不受其他腳本加載的影響。

          defer 和 async

          我們可以在 script 定義 deferasync ,使整個腳本加載方式更加友好。比如:被修飾的腳本在 head 中,將不會阻斷 body 內容的展示

          注意: defer 修飾的腳本將延遲到 body 中所有定義的腳本之后,DOM(頁面內容)加載完之前觸發async 不會像 defer 一樣等待 body 中的腳本,而是當前腳本一加載完畢就觸發。

          <head>
            <!-- 如果上面沒有其他響應慢的腳本,解析到此處加載完后將立馬執行 -->
            <script async src="/js/scriptAsync.js?t=3000"></script>
            <!-- 1 秒,延遲到 DOM 加載完畢 -->
            <script defer src="/js/scriptDefer.js?t=1000"></script>
          </head>
          <body>
            <script>
              document.addEventListener('DOMContentLoaded', function () {
                console.log('[ready] document');
              });
            </script>
            <script>
              var newScript = document.createElement('script');
              newScript.type = 'text/javascript';
              newScript.src = '/js/dynamicScript.js?t=8000';
              document.getElementsByTagName('head')[0].appendChild(newScript);
              // 腳本加載完畢
              if (newScript.readyState) {
                newScript.onreadystatechange = function () {
                  if (newScript.readyState == 'loaded' || newScript.readyState == 'complete') {
                    console.log('dynamicScript.js loaded');
                  }
                };
              } else {
                newScript.onload = function () {
                  console.log('dynamicScript.js loaded');
                };
              }
            </script>
            <script>
              console.log('init foo', 0);
              var foo = 0;
            </script>
            <script src="/js/addTen.js?t=5000"></script>
            <div>我被渲染了</div>
            <script src="/js/addOne.js?t=1000"></script>
          </body>
          

          加載順序:

          已加載  1  秒
          已加載  2  秒
          scriptAsync.js
          已加載  3  秒
          已加載  4  秒
          addTen.js
          foo 10
          addOne.js
          foo 11
          scriptDefer.js
          [ready] document
          已加載  5  秒
          已加載  6  秒
          已加載  7  秒
          已加載  8  秒
          dynamicScript.js is running
          dynamicScript.js loaded
          已加載  9  秒
          已加載  10  秒
          

          本文使用 mdnice 排版

          么是JS延遲加載?

          JS延遲加載,也就是等頁面加載完成之后再加載JavaScript文件

          為什么讓JS實現延遲加載?

          js的延遲加載有助于提高頁面的加載速度。

          Js延遲加載的方式有哪些?一般有以下幾種方式:

          ·defer屬性

          ·async屬性

          ·動態創建DOM方式

          ·使用jQuery的getScript方法

          ·使用setTimeout延遲方法

          ·讓JS最后加載

          1、defer屬性

          HTML 4.01為<script>標簽定義了defer屬性。標簽定義了defer屬性元素中設置defer屬性,等于告訴瀏覽器立即下載,但延遲執行標簽定義了defer屬性。

          用途:表明腳本在執行時不會影響頁面的構造。也就是說,腳本會被延遲到整個頁面都解析完畢之后再執行在<script>元素中設置defer屬性,等于告訴瀏覽器立即下載,但延遲執行

          <!DOCTYPE html>
          <html>
          <head>
          	<script src="test1.js" defer="defer"></script>
          	<script src="test2.js" defer="defer"></script>
          </head>
          <body>
          <!--這里放內容-->
          </body>
          </html>

          說明:雖然<script>元素放在了<head>元素中,但包含的腳本將延遲瀏覽器遇到</html>標簽后再執行HTML5規范要求腳本按照它們出現的先后順序執行。在現實當中,延遲腳本并不一定會按照順序執行defer屬性只適用于外部腳本文件。支持HTML5的實現會忽略嵌入腳本設置的defer屬性

          2、async屬性

          HTML5 為<script>標簽定義了async屬性。與defer屬性類似,都用于改變處理腳本的行為。同樣,只適用于外部腳本文件。標簽定義了async屬性。與defer屬性類似,都用于改變處理腳本的行為。同樣,只適用于外部腳本文件。

          目的:不讓頁面等待腳本下載和執行,從而異步加載頁面其他內容。異步腳本一定會在頁面 load 事件前執行。不能保證腳本會按順序執行

          <!DOCTYPE html>
          <html>
          	<head>
          		<script src="test1.js" async></script>
          		<script src="test2.js" async></script>
          	</head>
          <body>
          <!--這里放內容-->
          </body>
          </html>

          async和defer一樣,都不會阻塞其他資源下載,所以不會影響頁面的加載。

          缺點:不能控制加載的順序

          3、動態創建DOM方式

          //這些代碼應被放置在</ body>標簽前(接近HTML文件底部)
          <script type="text/javascript">
          	function downloadJSAtOnload() {
          		varelement = document .createElement("script");
          		element.src = "defer.js";
          		document.body.appendChild(element);
          	}
          	if (window. addEventListener)
          		window.addEventListener("load" ,downloadJSAtOnload, false);
          	else if (window.attachEvent)
          		window.attachEvent("onload", downloadJSAtOnload) ;
          	else
          		window. onload =downloadJSAtOnload;
          </script>

          4、使用jQuery的getScript()方法

          $.getScript("outer.js" , function(){	//回調函數,成功獲取文件后執行的函數
          	console.log(“腳本加載完成")
          });

          5、使用setTimeout延遲方法的加載時間延遲加載js代碼,給網頁加載留出更多時間

          <script type="text/javascript" >
          	function A(){
          		$.post("/1ord/1ogin" ,{name:username,pwd:password},function(){
          			alert("Hello");
          		});
          	}
          	$(function (){
          		setTimeout('A()', 1000);	//延遲1秒
          	})
          </script>

          6、讓JS最后加載

          把js外部引入的文件放到頁面底部,來讓js最后引入,從而加快頁面加載速度例如引入外部js腳本文件時,如果放入html的head中,則頁面加載前該js腳本就會被加載入頁面,而放入body中,則會按照頁面從上倒下的加載順序來運行JavaScript的代碼。所以我們可以把js外部引入的文件放到頁面底部,來讓js最后引入,從而加快頁面加載速度。

          上述方法2也會偶爾讓你收到Google頁面速度測試工具的“延遲加載javascript”警告。所以這里的解決方案將是來自Google幫助頁面的推薦方案。

          //這些代碼應被放置在</body>標簽前(接近HTML文件底部)
          
          <script type= "text/javascript">
          	function downloadJSAtonload() {
          		var element = document.createElement("script");
          		element.src = "defer.js";
          		document.body.appendChild(element);
          	}
          	if (window.addEventListener)
          		window.addEventListener("load", downloadJSAtOnload, false);
          	else if (window.attachEvent )
          		window.attachEvent("onload", downloadJSAtonload);
          	else window.onload = downloadJSAtOnload;
          </script>

          這段代碼意思等到整個文檔加載完后,再加載外部文件“defer.js”。

          使用此段代碼的步驟:

          6.1)復制上面代碼

          6.2)粘貼代碼到HTML的標簽前 (靠近HTML文件底部)

          6.3)修改“defer.js”為你的外部JS文件名

          6.4)確保文件路徑是正確的。例如:如果你僅輸入“defer.js”,那么“defer.js”文件一定與HTML文件在同一文件夾下。

          注意:

          這段代碼直到文檔加載完才會加載指定的外部js文件。因此,不應該把那些頁面正常加載需要依賴的javascript代碼放在這里。而應該將JavaScript代碼分成兩組。一組是因頁面需要而立即加載的javascript代碼,另外一組是在頁面加載后進行操作的javascript代碼(例如添加click事件。


          主站蜘蛛池模板: 国产人妖视频一区二区破除| 无码人妻精品一区二区三区久久久| 精品乱子伦一区二区三区高清免费播放 | 好吊妞视频一区二区| 国产精品一区二区四区| 国产一区麻豆剧传媒果冻精品| 无码少妇A片一区二区三区| 无码国产精品一区二区免费式芒果| 精品视频一区二区三区| 色噜噜一区二区三区| 濑亚美莉在线视频一区| 国产成人欧美一区二区三区| 国产成人亚洲综合一区| 国产吧一区在线视频| 精品亚洲AV无码一区二区三区| 日韩精品视频一区二区三区| 夜夜嗨AV一区二区三区| 亚洲毛片αv无线播放一区| 99久久精品国产高清一区二区 | 精品国产一区二区三区香蕉| 国产福利91精品一区二区| 中文字幕一区二区精品区| 精品日韩亚洲AV无码一区二区三区| 亚洲国产成人一区二区精品区| 中文字幕人妻无码一区二区三区| 中文字幕一区二区免费| 精品久久一区二区三区| 久久久老熟女一区二区三区| 精品熟人妻一区二区三区四区不卡 | 国产丝袜无码一区二区视频| 福利一区福利二区| 国产AⅤ精品一区二区三区久久 | 无码精品人妻一区二区三区中 | 亚洲欧美成人一区二区三区 | 无码精品黑人一区二区三区| 亚洲香蕉久久一区二区| 国产精品一区二区香蕉| 精品一区二区三区色花堂| 午夜精品一区二区三区免费视频| 日日摸夜夜添一区| 国产成人免费一区二区三区|