天天看點

動态加載js如何保證順序執行?

我們知道,可以使用建立script标簽的方法來動态加載js檔案。

但随之帶來問題,如果建立多個script标簽來加載多個js檔案,這些檔案是異步并行加載的,預設添加了async屬性,最後執行順序不能保證是當初的scrpit标簽建立順序。

也就是說,如果我們引入的js之間有依賴關系,能不能加載成功不報錯 就是個随機事件了~

function createScript (src) {
    let script = document.createElement('script');
    script.src = src;
    document.body.appendChild(script);
};

createScript('a.js');
createScript('b.js'); 
// 假定b.js依賴a.js的檔案内容,因為網絡等原因可能b.js會在a.js之前加載完,導緻報錯
           

是以上面的代碼需要改進一下,通過監聽onload 和 onreadystatechange,確定a.js全部執行完成後,才能加載b.js。

參考http://www.w3help.org/zh-cn/causes/BX9013

// 方法一:
function createScript(url, success) {
  let script = document.createElement('script');
  script.src = url;
  success = success || function(){};
  script.onload = script.onreadystatechange = function() {
    // onreadystatechange和readyState針對ie
    if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
      success();
      this.onload = this.onreadystatechange = null;
      this.parentNode.removeChild(this);
    }
  }
  document.body.appendChild(script);
}
//順序執行加載外部 JS 檔案
createScript('a.js',function (){
   createScript('b.js',function (){
      alert('ok');
   });
});
           

到這裡就實作了動态加載js也能按順序執行的效果。

然而這個解決方法實在是太長,是以找到了另一種簡單粗暴的方法。

一開始script标簽中的async屬性不管是設定為true or false,都會在下載下傳該js腳本的同時,不阻塞頁面其他操作,且下載下傳完會立即執行。

在Kyle Simpson的提議下,動态插入的script标簽預設設定了屬性async=true,以期異步執行。同時也可以設定async=false來保持插入順序執行。(并行加載,順序執行)

The proposal basically preserves Webkit (and IE)'s current default behavior, but in a more formalized and flexible way, which is that any script inserted dynamically will essentially default to behaving like a parser-inserted script with `async=true`.

該提議基本上保留了Webkit(和IE)的目前預設行為,但是以一種更加形式化和靈活的方式,即,動态插入的任何腳本本質上都将預設表現為具有async = true的解析器插入腳本。

The requested extension is that a dynamically inserted script tag which has `async=false` set on it will go into a separate loading "queue" of sorts, in that all such scripts with `async=false` will load in parallel but will execute in insertion order.

要求的擴充是動态插入的腳本标簽上設定了async = false,它将進入單獨的各種加載“隊列”,因為所有帶有async = false的腳本都将并行加載但将執行按照插入順序。

是以我們隻需要加上 "async=false" :

// 方法二:
function createScript (src) {
    let script = document.createElement('script');
    script.src = src;
    // 保證JS順序執行!
    script.async = false
    document.body.appendChild(script);
};

createScript('a.js');
createScript('b.js'); 
           

       一行代碼解決問題,完美😼

       這裡延伸一下,外部引入腳本的script标簽可以有defer和async屬性,講道理他們應該是這樣的:

  1. defer屬性:并行加載,文檔全部解析完成後(DOMContentLoaded 事件觸發之前),順序執行。<script defer src="index.js"></script>
  2. async屬性:并行加載,加載完立即執行,無序。<script async src="index.js"></script>
  3. 如果都沒有,就會阻塞程序,加載執行完再繼續往下解析文檔。<script src="index.js"></script>

       是以理論上,如果在createScript裡加入script.defer也可以保證順序執行。但實際上試了卻發現順序也不能确定。在JavaScript進階程式設計(第3版)中有這麼一句:

在現實當中,延遲腳本不一定按順序執行,也不一定會在DOMContentLoaded 事件觸發前執行,是以最好隻包含一個延遲腳本。

 是以還是“async = false”大法好👌

繼續閱讀