我們知道,可以使用建立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屬性,講道理他們應該是這樣的:
- defer屬性:并行加載,文檔全部解析完成後(DOMContentLoaded 事件觸發之前),順序執行。<script defer src="index.js"></script>
- async屬性:并行加載,加載完立即執行,無序。<script async src="index.js"></script>
- 如果都沒有,就會阻塞程序,加載執行完再繼續往下解析文檔。<script src="index.js"></script>
是以理論上,如果在createScript裡加入script.defer也可以保證順序執行。但實際上試了卻發現順序也不能确定。在JavaScript進階程式設計(第3版)中有這麼一句:
在現實當中,延遲腳本不一定按順序執行,也不一定會在DOMContentLoaded 事件觸發前執行,是以最好隻包含一個延遲腳本。
是以還是“async = false”大法好👌