document.createElement('script') 和 DOMContentLoaded 執行順序


上篇在翻譯一篇文章時看到:

腳本不阻塞DOMContentLoaded

此規則有兩個特例:

  1. 腳本有 async 屬性,我們稍后會提到此屬性,不會阻塞 DOMContentLoaded
  2. 腳本由 document.createElement('script') 動態生成,然后加入到html文檔,也不會阻塞 DOMContentLoaded

上邊提到的第一條規則我之前是知道的: async 腳本會在 load 事件之前發生,但不能保證 DOMContentLoaded 事件先后的執行順序 ,也就是說不一定阻塞渲染(如果文件過大,下載時間長(瀏覽器渲染和js文件下載並行執行),有可能在瀏覽器渲染完成后才下載完,也就是在DOMContentLoaded事件觸發后才執行)

那么,第二條規則的我之前沒有留意過,有可能跟平時使用的少有關(幾乎沒有使用過)。我第一時間會質疑:這個說法成立嗎?

實踐出真知,實例代碼如下:

loadScripts([
    './dynamic-script.js',
  ], function () {
    alert('dynamic-script: loaded')
  })

  document.addEventListener('DOMContentLoaded', () => {
    alert('DOMContentLoaded')
  })
  
  window.onload = () => {
    alert('onload')
  }

  function loadScripts(array,callback){
    var loader = function(src,handler){
      var script = document.createElement("script");
      script.src = src;
      script.onload = script.onreadystatechange = function(){
        script.onreadystatechange = script.onload = null;
        handler();
	console.log(script.async) // 注1
      }
      var head = document.getElementsByTagName("head")[0];
      (head || document.body).appendChild( script );
    };
    (function run(){
      if(array.length!=0){
        loader(array.shift(), run);
      }else{
        callback && callback();
      }
    })();
  }
// dynamic-script.js
alert('dynamic-script-content')

執行結果如下:

  1. 打印 'DOMContentLoaded'
  2. 打印 'dynamic-script-content'
  3. 打印 'dynamic-script: loaded'
  4. 打印 true(控制台)
  5. 打印 'onload'

結果跟上邊的描述是一致的。。。

網上查閱資料解釋:JavaScript異步加載的三種方式——async和defer、動態創建script

在沒有定義defer和async之前,異步加載的方式是動態創建script,通過window.onload方法確保頁面加載完畢再將script標簽插入到DOM中。

MDN解釋: <script>

[1] 在不支持該async屬性的舊瀏覽器中,解析器插入的腳本會阻塞解析器;插入腳本的腳本在 IE 和 WebKit 中異步執行,但在 Opera 和 4.0 之前的 Firefox 中同步執行。在 Firefox 4.0 中,asyncDOM 屬性默認為true用於腳本創建的腳本,因此默認行為與 IE 和 WebKit 的行為相匹配。要請求在document.createElement("script").async評估為的瀏覽器true(例如 Firefox 4.0)中以插入順序執行插入腳本的外部腳本,請.async=false在要維護順序的腳本上設置。永遠不要document.write()從async腳本中調用。在 Gecko 1.9.2 中,調用document.write()具有不可預測的效果。在 Gecko 2.0 中,document.write()從async 腳本無效(除了向錯誤控制台打印警告)。

此外,還有一篇文章提到: DOMContentLoaded talk about blocking and rendering - analysis and resource loading html page events

為了驗證上邊的解釋,我在實例代碼中(// 注1)處對script的async值進行了輸出,由 document.createElement("script") 創建的腳本確實是異步的。

記憶技巧

首先我們來看 document.createElement("script") ,這里邊有個 document... 也就是說我們需要使用 document 對象,何時才能訪問到 document 對象? 答案顯而易見:dom樹構建完畢時(此時頁面還沒有生成cssom,render tree,布局,繪制balabala)。。。我們考慮下這段動態插入的腳本肯定不是很重要的(如果很重要,需要在關鍵渲染路徑之前執行,肯定會寫為內聯腳本),既然這段腳本不是很重要,那就不能影響關鍵渲染路徑的性能,那就等瀏覽器首屏渲染完成后(這句可能不那么准確)再下載執行也不遲。。。所以設計者就將其放在 DOMContentLoaded 之后下載執行,從而實現異步加載,也就是不阻塞 DOMContentLoaded 事件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM