同步加載和執行JS的情況
在HTML頁面的</body>表情之前添加的所有<script>標簽,無論是直接嵌入JS代碼還是引入外部js代碼都是同步執行的,這里的同步執行指的是在加載好和執行完JS代碼之前整個瀏覽器的界面都是阻塞的。
靜態加載時
內嵌代碼和引入js代碼都是同步加載。
動態加載時
通過document.write('<script src="xxx.js"></script>');的方式加入的代碼也是同步加載的,但是要注意一點,這里開始加載和解析xxx.js是在執行完成當前的<script>標簽之后,所以下面的代碼會報錯(我們假設test.js中創建了全局的p對象,且該對象存在sayHi的方法):
1 <script> 2 console.log("sync load js"); 3 document.write('<script src="test.js"></script>'); 4 console.log(p.sayHi()); 5 </script>
根據我們說的開始加載和解析xxx.js是在執行完成當前的<script>標簽之后,可以把上面的代碼轉換如下:
1 <script> 2 console.log("sync load js"); 3 console.log(p.sayHi()); 4 </script> 5 <script src="test.js"></script>
這樣必然會出錯,如下則可以正常執行:
1 <script> 2 console.log("sync load js"); 3 document.write('<script src="test.js"></script>'); 4 </script> 5 <script> 6 console.log(p.sayHi()); 7 </script>
document.write方式同步加載外部js文件無論是內嵌的寫法還是寫在外部js文件中,效果都是一樣的,都會同步加載和執行。
異步加載和執行JS的情況
靜態加載時
對於內嵌的JS代碼只能同步執行,對於通過<script>標簽引入的外部js文件,可以通過設定defer和async兩個屬性改變為異步加載執行。
defer
代碼如下:<script src="xxx.js" defer></script>,該屬性在HTML4.01引入,告訴瀏覽器立即下載該腳本(下載期間不阻塞頁面),但是等待遇到</html>標簽之后再執行,同時執行順序按照標簽出現順序進行執行。該腳本的執行一定會在頁面的load事件之前。
async
代碼如下:<script src="xxx.js" async></script>,該屬性在HTML5引入,告訴瀏覽器立即下載該腳本(下載期間不阻塞頁面),但是執行腳本時不能保證按照標簽出現順序執行。該腳本的執行一定會在頁面的load事件之前。
最佳實踐
由於瀏覽器的支持不同,所以最好的方法仍然是將<script src="xxx.js"></script>腳本放在頁面的最下方引入。
動態加載時
如果我們希望在頁面任意時刻動態異步加載js文件,可以通過使用DOM元素創建方法來實現:
1 var script = document.createElement("script"); 2 script.type = "text/javascript"; 3 script.src = "xxx.js"; 4 document.getElementsByTagName("head")[0].appendChild(script);
當script元素被添加到頁面之后便開始下載腳本文件。該技術的優點是:無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。當script文件下載完成后,返回的代碼通常被立即執行(除了FF和Opera,它們將等待此前的所有動態腳本節點執行完畢。)
唯一的問題是我們需要知道動態加載的腳本何時完成加載並可以使用,FF, Opera, Chrome和Safari3+會在節點接收完成后發出一個load事件;IE則是發出一個readystatechange事件,<script>元素有一個readyState屬性,它的值隨着下載過程而改變。readyState有5種取值:uninitialized(默認狀態),loading(下載開始),loaded(下載完成),interactive(下載完成但尚不可用),complete(所有數據已准備好)。
最終的代碼如下:
1 function loadScript(url, callback){ 2 var script = document.createElement("script"); 3 script.type = "text/javascript"; 4 if (script.readyState) { // IE 5 script.onreadystatechange = function() { 6 if (script.readyState == "loaded" || script.readyState == "complete") { 7 script.onreadystatechange = null; 8 callback(); 9 } 10 }; 11 } else { // FF, Chrome, Opera, ... 12 script.onload = function() { 13 callback(); 14 }; 15 } 16 script.src = url; 17 document.getElementsByTagName("head")[0].appendChild(script); 18 }
如果要加載多個文件,並且要保證順序,則可以采用將上述函數串聯的方式實現。
1 loadScript("../scripts/test1.js", function(){ 2 loadScript("../scripts/test2.js", function(){ 3 loadScript("../scripts/test3.js", function(){ 4 test3.print("hi, i'm abc."); 5 }) 6 }); 7 });
如果覺得這種嵌入的寫法太不爽,也可以稍作修改改為Promise的寫法。