在《瀏覽器環境下JavaScript腳本載入與執行探析之defer與async特性》中,我們研究了延遲腳本(defer)和非同步腳本(async)的執行時機、瀏覽器支持情況、瀏覽器bug以及其他的細節問題。而除了defer和async特性,動態腳本和Ajax腳本註入也是兩種常用的創建無阻塞腳本的方法。...
在《瀏覽器環境下JavaScript腳本載入與執行探析之defer與async特性》中,我們研究了延遲腳本(defer)和非同步腳本(async)的執行時機、瀏覽器支持情況、瀏覽器bug以及其他的細節問題。而除了defer和async特性,動態腳本和Ajax腳本註入也是兩種常用的創建無阻塞腳本的方法。總的來看,這兩種方法都能達到腳本載入不影響頁面解析和渲染的作用,但是在不同的瀏覽器中,這兩種技術所創建的腳本的執行時機還是有一定差異,今天我們再來探討一下通過動態腳本技術和Ajax註入的腳本在這些方面的特性。
代碼準備:
我們使用《瀏覽器環境下JavaScript腳本載入與執行探析之代碼執行順序》2.3節中的loadScript函數來添加動態腳本,同時使用這篇文章2.4節中的loadXhrScript函數來實現Ajax腳本註入。我們把這兩個函數都放在util.js中。
另外,本文使用的CHROME的版本為47.0.2526.80,firefox的版本為43.0.4,opera版本為30.0.1835.125。
1 動態腳本
1.1動態腳本的執行時機問題
我們在《瀏覽器環境下JavaScript腳本載入與執行探析之defer與async特性》中2.3節DEMO的基礎上,增加三個外部js文件:
dynamic1.js
1 test += "我是head外部動態腳本\n";
dynamic2.js
1 test += "我是body外部動態腳本\n";
dynamic3.js
1 test += "我是底部外部動態腳本\n";
1.1.1 DEMO1:動態腳本的執行時機初探
HTML的代碼為:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"/> 5 <title>Dynamic Script Test</title> 6 <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> 7 <script src="util.js"></script> 8 <script type="text/javascript">var test = "";</script> 9 <script> 10 loadScript("dynamic1.js"); 11 </script> 12 <script> 13 test += "我是head內部腳本\n"; 14 </script> 15 <script src="1.js" type="text/javascript"></script> 16 </head> 17 <body> 18 <button id="test">點擊一下</button> 19 <script> 20 loadScript("dynamic2.js"); 21 </script> 22 <script src="2.js" type="text/javascript"></script> 23 </body> 24 <script> 25 loadScript("dynamic3.js"); 26 </script> 27 <script src="3.js" type="text/javascript"></script> 28 <script> 29 $(function(){ 30 test += "我是DOMContentLoaded裡面的腳本\n"; 31 }) 32 window.onload = function(){ 33 test += "我是window.onload裡面的腳本\n"; 34 var button = document.getElementById("test"); 35 button.onclick = function(){ 36 console.log(test); 37 } 38 } 39 </script>
在代碼中,我們先後將3個動態腳本文件加入到HTML的<head>標簽中,同時通過與正常外部腳本和內部腳本的執行來進行比較,我們看一下不同瀏覽器中的執行結果:
IE7 | IE9 | IE10 | CHROME | firefox | opera |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
註:firefox和opera中執行結果可能會變化
從上面的例子中,我們可以看出,在不同瀏覽器中,動態腳本的執行時機差異還是比較大的,但以下兩點是可以明確的:
[1]動態腳本確實可以起到不阻塞後續腳本的作用,即延遲作用,但是這個延遲作用卻不一定能夠持續到所有的正常腳本都執行完畢之後,也無法保證能夠延遲到DOMContentLoaded之後
[2]動態腳本執行的先後順序是無法保證的,這一點在http://www.cnblogs.com/sanshi/archive/2011/02/28/1967367.html這篇文章以及後續的幾篇文章中進行了詳細的解釋
從這個DEMO中還可以看出,動態腳本的執行時機具有較大的不確定性,雖然在DEMO1中,動態腳本都在DOMContentLoaded事件之後執行,但卻也並不意味著動態腳本就不會阻塞DOMContentLoaded,我們通過DEOM2來看一下:
1.1.2 DEMO2:動態腳本對DOMContentLoaded的阻塞
我們把DEMO1中的第27行內碼修改為:
1 <script src="/delayfile.php?url=http://localhost/js/load/3.js&delay=5" type="text/javascript"></script>
我們利用《瀏覽器環境下JavaScript腳本載入與執行探析之代碼執行順序》中的delayfile.php,將3.js的返回延遲5秒鐘,我們知道,如果是defer延遲腳本,無論正常外部腳本延遲了多長時間,defer腳本還是會在正常外部腳本之後執行的,但是動態腳本卻不是這樣了,我們看一下修改後的代碼在瀏覽器中的執行順序:
IE7 | IE9 | IE10 | CHROME | firefox | opera |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
註:firefox和opera中執行結果可能會變化
可以看到,在某個正常腳本載入時間較長的時候,動態腳本的執行明顯提前,無論在IE還是CHROME、firefox和opera中,均在DOMContentLoaded之前執行,因此我們可以初步判斷,動態腳本的執行時機是不確定的,在不同瀏覽器的執行時機也都存在差異,但總的來看應該是在代碼載入結束之後,並且線程中沒有JavaScript代碼執行的某個時間,但不同瀏覽器對這個時間有著不同的把握。
因此,動態腳本是否會阻塞DOMContentLoaded也是不確定的,因為動態腳本可能在DOMContentLoaded觸發之前,也可能在觸發之後執行。而且由於IE<=8不支持真正的DOMContentLoaded事件,jQuery在IE<=8中也是模擬判斷該事件的發生(下一篇會專門講解DOMContentLoaded事件),一定程度上也會對我們上述代碼的執行結果造成影響。
1.1.3 DEMO3:動態腳本與defer
我們知道,defer腳本是有著相對明確的執行時機的,即頁面解析完成之後,DOMContentLoaded觸發之前載入並且執行,事實上,二者之間在執行時機上並不存在什麼關聯,但是在實際實驗中發現,動態腳本可能會在defer腳本之前或者之後執行,但卻不會打斷defer腳本的執行,我們再引入《瀏覽器環境下JavaScript腳本載入與執行探析之defer與async特性》中2.3節的DEMO中的defer腳本,修改HTML代碼如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"/> 5 <title>Dynamic Script Test</title> 6 <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> 7 <script src="util.js"></script> 8 <script> 9 $(function(){ 10 test += "我是DOMContentLoaded裡面的腳本\n"; 11 }) 12 window.onload = function(){ 13 test += "我是window.onload裡面的腳本\n"; 14 var button = document.getElementById("test"); 15 button.onclick = function(){ 16 console.log(test); 17 } 18 } 19 </script> 20 <script type="text/javascript">var test = "";</script> 21 <script> 22 loadScript("dynamic1.js"); 23 </script> 24 <script> 25 test += "我是head內部腳本\n"; 26 </script> 27 <script src="defer1.js" type="text/javascript" defer="defer"></script> 28 <script src="1.js" type="text/javascript"></script> 29 </head> 30 <body> 31 <button id="test">點擊一下</button> 32 <script> 33 loadScript("dynamic2.js"); 34 </script> 35 <script src="defer2.js" type="text/javascript" defer="defer"></script> 36 <script src="2.js" type="text/javascript"></script> 37 </body> 38 <script> 39 loadScript("dynamic3.js"); 40 </script> 41 <script src="defer3.js" type="text/javascript" defer="defer"></script> 42 <script src="3.js" type="text/javascript"></script> 43 </html>
註:firefox和opera中執行結果可能會變化
我們增加了幾個defer的腳本,再來看一下各個瀏覽器中的執行結果:
IE7 | IE9 | IE10 | CHROME | firefox | opera |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
從實驗結果可以看出,動態腳本的執行時機與defer腳本並沒有直接的關係,錶面上看起來在CHROME和firefox中,延遲腳本總是在動態腳本之前執行,在《前端優化-Javascript篇(2.非同步載入腳本)》一文中提到過“ScriptDOM和defer同時都可以執行,在不同瀏覽器中它們的優先順序的不一樣的。在Firfox和Chrome中,ScriptDOM的優先順序比defer低,而在IE中情況則相反。”,其實這種優先順序應該是不存在的,我們只需要將defer腳本加一個載入延遲,那麼動態腳本的執行就會先於defer腳本了。
1.2 動態腳本執行問題總結
我們再來總結一下動態腳本的執行問題:
[1]首先,動態腳本確實能夠在一定程度上起到延遲腳本執行的作用,但由於動態腳本的執行時機的不確定性,這種延遲作用的效果也是未知的。
[2]其次,動態腳本的執行順序不一定會按照添加的順序,這是動態腳本技術比較大的問題之一,最簡單的解決方式就是使用回調函數,監聽腳本的載入狀態,在一個腳本載入結束後再動態添加下一個腳本。
[3]動態腳本沒有確切的執行時機,當通過DOM的appendChild、insertBefore等方法將script元素添加到DOM中時,就會去載入JS腳本,腳本的執行應該是在載入結束後的某個時機,不同瀏覽器對這個時機的處理差異比較大,比如在IE中,應該是採取儘快執行的策略,也就是在載入結束後儘快尋找時機執行代碼
[4]動態腳本可能會在DOMContentLoaded觸發之前或者之後執行,因此無法確定其是否會阻塞DOMContentLoaded。而在一般情況下,動態腳本都會阻塞window.onload,但是也會存在動態腳本在window.onload觸發之後執行,從而不會阻塞window.onload
2 Ajax註入腳本
2.1Ajax註入腳本的執行時機問題
Ajax腳本註入技術有兩種模式:同步載入和非同步載入,同步載入的情況比較簡單,腳本的載入和執行會阻塞後面代碼的執行,直到註入的代碼被載入和執行完畢。我們主要討論非同步模式下的情況:
2.1.1 DEMO4:Ajax註入腳本的執行問題初探
我們再添加3個外部文件:
ajax1.js
1 test += "我是head外部AJAX腳本\n";
ajax2.js
1 test += "我是body外部AJAX腳本\n";
ajax3.js
1 test += "我是底部外部AJAX腳本\n";
HTML的代碼為:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"/> 5 <title>Ajax Script Test</title> 6 <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script> 7 <script src="util.js"></script> 8 <script type="text/javascript">var test = "";</script> 9 <script> 10 $(function(){ 11 test += "我是DOMContentLoaded裡面的腳本\n"; 12 }) 13 window.onload = function(){ 14 test += "我是window.onload裡面的腳本\n"; 15 var button = document.getElementById("test"); 16 button.onclick = function(){ 17 console.log(test); 18 } 19 } 20 </script> 21 <script> 22 loadXhrScript("ajax1.js",true); 23 </script> 24 <script> 25 test += "我是head內部腳本\n"; 26 </script> 27 <script src="1.js" type="text/javascript"></script> 28 </head> 29 <body> 30 <button id="test">點擊一下</button> 31 <script> 32 loadXhrScript("ajax2.js",true); 33 </script> 34 <script src="2.js" type="text/javascript"></script> 35 </body> 36 <script> 37 loadXhrScript("ajax3.js",true); 38 </script> 39 <script src="3.js" type="text/javascript"></script> 40 </html>
在這段代碼中,我們分別在<head>標簽內部、<body>標簽內部、<body>標簽外部共添加了3個註入腳本,通過正常引入的腳本作為參照,我們看一下在瀏覽器中的執行結果:
IE7 | IE9 | IE10 | CHROME | firefox | opera |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
我是head內部腳本 |
註:firefox、opera、IE中的執行結果可能會變化
從這個執行結果中,我們就可以看到,Ajax註入腳本的執行時機具有更大的不確定性,事實上,與動態腳本類似,Ajax註入腳本的載入過程也是非同步的,因此,完成載入的時間首先是不確定的,其次,瀏覽器在腳本載入完成後何時執行載入的代碼同樣也是不確定的,對於非同步模式下的Ajax註入腳本的執行時機,我們總結如下:
[1]Ajax註入的腳本也具有一定的延遲作用,但是與動態腳本類似,延遲的時間是不確定的。
[2]Ajax註入腳本的執行順序是無序的,雖然DEMO4中的例子看起來註入的腳本都是按照添加的順序執行的,但是只要稍微理解非同步以及動態腳本執行順序問題,就應該能夠明白這一點。
[3]Ajax註入腳本的執行時機也是不確定的,與腳本的載入時間以及瀏覽器的處理機制有關。
[4]由於上述的幾點,Ajax註入的腳本可能會阻塞DOMContentLoaded,也可能會阻塞window.onload。
由於個人經驗尚淺,同時文章中所做的實驗的瀏覽器覆蓋率不夠大,歡迎大家指正問題與討論~~