上篇在翻譯一篇文章時看到:
腳本不阻塞DOMContentLoaded
此規則有兩個特例:
- 腳本有 async 屬性,我們稍后會提到此屬性,不會阻塞 DOMContentLoaded
- 腳本由 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')
執行結果如下:
- 打印 'DOMContentLoaded'
- 打印 'dynamic-script-content'
- 打印 'dynamic-script: loaded'
- 打印 true(控制台)
- 打印 '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 事件。
