js在瀏覽器中性能,能夠認爲是開發者所面臨的最嚴重的可用性問題了,這個問題由於js的阻塞特性變得很複雜,也就是說瀏覽器在執行js代碼時,不能同時作其餘任何事情。事實上,多數瀏覽器使用單一進程來處理用戶界面刷新和js腳本的執行,因此只能同一時刻作一件事,js的執行過程耗時越久,瀏覽器等待響應的時間就越長。javascript
簡單的說,這意味着<script>標籤每次出現都霸道地讓頁面等待腳本的解析和執行。不管當前的js代碼時內嵌仍是外連接,頁面的下載和渲染都必須停下來等腳本的執行完成。這是頁面生存週期中的必要環節,由於腳本執行過程當中可能會修改頁面內容。一個典型例子就死document.write().咱們看到的廣告就是這麼搞的。css
腳本的位置html
html4規範指出<script>標籤能夠放在html文檔的<head>或<body>中,並容許出現屢次。按照慣例,<script>標籤用來加載出如今css加載的<link>標籤後。理論上來講,把樣式和行爲有關的腳本放在一塊兒,並先加載它們,這樣作有助於頁面的渲染和交互的正確性。java
可是,這樣存在十分嚴重的性能問題,在<head>標籤中加載js文件,因爲腳本會阻塞頁面的渲染,直到它們所有下載並執行完成後,頁面的渲染的纔會執行。要知道,瀏覽器在解析到<body>標籤以前,不會渲染頁面的任何內容,把腳本放在頁面頂部會致使明顯的延遲,會有明顯的白屏時間,用戶沒法瀏覽內容,也沒法與頁面進行交互。瀑布圖能夠幫咱們更清楚地理解性能發生的緣由。所以js要放在<body>標籤的底部。web
組織腳本gulp
每個<script>標籤初始下載時都會阻塞頁面渲染,因此減小頁面包含的<script>標籤數量有助於改善這一狀況,這不只僅是針對外鏈腳本,內鏈腳本的數量也要限制,這個問題在處理外鏈腳本文件時略有不一樣,由於http請求還會帶來額外的性能開銷,所以下載單個100kb的文件將比下載四個25kb的文件更快,也就是說,減小頁面中腳本文件數量將會改善性能。瀏覽器
一般一個大型網站或網絡應用須要依賴數個js文件,咱們能夠把多個文件合併成一個,這樣就只需引用一個<script>標籤了。文件合併能夠利用如今的不少構建工具,grunt,gulp等,都很方便。緩存
無阻塞的腳本安全
js傾向於阻止瀏覽器的某些處理過程,如http請求和用戶界面更新,這是開發者所面臨的最顯著的性能問題。減小js文件大小並限制http請求僅僅是建立響應迅速的Web應用的第一步,web應用的功能愈來愈強大豐富,所須要的腳本代碼也就越多,因此精簡代碼並不老是可行,儘管下載單個較大的js文件只產生一次http請求,卻會鎖死瀏覽器一大段時間,這樣顯然不是良好的用戶體驗,爲避免這種狀況,咱們須要的是向頁面中逐步加載js文件,這樣作從某種程度上不會阻塞瀏覽器。服務器
無阻塞腳本的祕訣在於,在頁面加載完後才加載js代碼,用專業術語說,這意味着window對象的load事件觸發後再下載腳本,有不少方式能夠實現這一效果。
《1》延遲的腳本
html4爲<script>標籤訂義了一個擴展屬性,defer。defer屬性指明本元素所含的腳本不會修改dom,所以代碼能夠安全的延遲執行。這個屬性目前已經被全部的主流瀏覽器支持了。另外說說HTML5 中引入的async屬性,用於異步加載腳本。async和defer的相同點是採用並行下載,在下載的過程不會產生阻塞,區別在於執行的時機,async是加載完成後自動執行,而defer須要等待頁面完成後才執行。
帶有defer屬性的<script>標籤能夠放置在文檔的任何位置,對應的js文件將在解析到<script>標籤時開始下載,但不會執行,直到dom加載完成後(onload事件被觸發前)所以這類文件能夠與頁面中的其餘資源並行下載。
<script type='type/javascript ' src="xiaoai.js" defer></script>
示例:
<html>
<head>
<title> script defer</title>
</head>
<body>
<script defer>
alert(1);
</script>
<script>
alert(2);
</script>
<script>
window.onload=function(){
alert(3);
}
</script>
</body>
</html>
這段代碼彈出三次提示框,若你的瀏覽器支持defer,彈出的順序爲2,1,3;而不支持defer的的瀏覽器則是1,2,3。請注意,帶有defer屬性的瀏覽器不是跟在第二個執行,而是在onload事件以前執行。
《2》動態腳本
因爲DOM的存在,你能夠用js建立HTML中幾乎全部內容。其緣由在於,<script>元素與頁面其餘元素並沒有差別:都能經過DOM進行引用,都能在文檔中移動,刪除或是被建立。用標準的DOM方法能夠很容易的建立一個新的<script>元素:
var script=document.createElement(‘script’);
script.type="text/javascript";
script.src="file1.js";
document.getElementsByTagName('head')[0].appendChild(script);
這個新建立的<script>元素加載了file1.js文件。文件在該元素被添加到頁面時開始下載。這種技術的重點在於:不管在什麼時候啓動下載,文件的下載和執行過程不會阻塞頁面的其餘進程。你甚至能夠將代碼放到頁面<head>區域而不會影響頁面其餘部分(用於下載文件的http連接自己的影響除外)。
另外,要注意,把新建立的<script>標籤添加到<head>標籤裏比添加到<body>裏更保險,尤爲是在頁面加載過程當中執行代碼時更是如此。當<body>中的內容沒有加載完成時,IE會拋出「操做已終止」的錯誤信息。
使用動態腳本節點下載文件時,返回的代碼一般會當即執行(除了Firefox和opera,它們會等待此前全部動態腳本節點執行完畢)。當腳本‘自執行’時,這種機制運行正常。可是當代碼只包含供頁面其餘腳本調用的接口時,就會有問題。在這種狀況下,你必須跟蹤並確保腳本下載完成且準備就緒。這能夠用動態<script>節點觸發的事件來實現。
Firefox,opera,Chrome和Safari以上的版本會在<script>元素接收完成時觸發一個load事件。所以能夠經過偵聽此事件來得到腳本加載完成時的狀態;
var script=document.createElement('script')
script.type='text/javascript';
script.onload=function(){
alert("script loaded");
};
script.src='file2.js';
document.getElementByTagName('head')[0].appendChild(script);
IE支持另外一種實現方式,它會觸發一個readyStatechange事件。<script>元素提供一個readyState屬性,它的值在外鏈文件的下載過程的不一樣階段會發生變化,該屬性有五種取值:
「uninitialized」 初始狀態
「loading」 開始下載
「loaded」 下載完成
「interactive」 數據完成下載但尚不可用
「complete」 全部數據已準備就緒
微軟的相關文檔代表,<script>元素生命週期中,並不是readyState的每一個取值都會被用到,實際應用中,最有用的兩個狀態就是「loaded」和「complete」。Ie在標識最終狀態時的值並不一致,有時<script>元素達到「loaded」狀態而從不會到達「complete」,有時候直接跳到「complete」而不通過「loaded」,使用這個屬性時最靠譜的方式是同時檢查這兩個狀態,只要其中任何一個觸發,就刪除事件處理器(以確保不會處理兩次)。
var script=document.createElement('script')
script.type="text/javascript";
script.onreadystatechange=function(){
if(script.readyState=="loaded"||script.readyState=="complete"){
script.onreadystatechange=null;
alert('script loaded');
};
script.src='file3.js';
document.getElementsByTagName('head')[0].appendChild(script);
}
以上是針對IE的動態加載js文件方法。
咱們須要一個兼容各瀏覽器的動態加載js文件的方法,下面是一個函數封裝了標準和IE特有的實現方法
function loadscript(url,callback)
{
var script=document.createElement('script')
script.type='text/javascript';
if(script.readyState){//IE
script.onreadystatechange=function(){
if(script.readyState=="loaded"||script.readystate=="complete"){
script.onreadystatechange=null;
callback();
}else{//其餘瀏覽器
script.onload=function()
{
callback();
};
}
script.src=url;
document.getElementsByTagName('head')[0].appendChild(script);
}
這個函數接收兩個參數:JavaScript文件的URL和完成加載後的回調函數。函數中使用了特徵檢測來決定腳本處理過程當中監聽哪一個事件。最後一步是給src屬性賦值,而後將<script>元素添加到頁面。loadscript()函數用法以下
loadscript("file1.js",function(){
alert(‘file is loaded’);
});
若是須要的話,你能夠動態加載盡肯能多的jswenjian 到頁面上,但必定要考慮清楚文件的加載順序。在全部的主流瀏覽器中,只有Firefox和opera能保證腳本會按照你指定的順序執行,其餘瀏覽器會按照從服務端返回的順序下載和執行代碼。所以能夠經過下面的串聯方式以確保下載順序。
loadscript(‘file1.js’,function(){
loadscript('file2.js',function(){
loadscript("file3.js",function(){
alert('all file is loaded');
});
});
});
下載順序爲 file1,file2,file3。
若是多個文件的下載順序很重要,更好的作法是把他們按正確的順序合併成一個文件。下載這個文件就會得到全部的代碼(因爲這個過程是異步的,所以文件大點不要緊)
總而言之,動態腳本加載憑藉着它在跨瀏覽器兼容性和易用的優點,成爲最通用的無阻塞加載js的解決方案。
《3》XMLhttpRequest 腳本注入
另外一種無阻塞加載腳本的方法是使用XMLHttpRequest(XHR)對象獲取腳本並注入頁面中。
此技術胡建立一個XHR對象,而後用它下載JavaScript文件,最後經過建立動態<script>元素將代碼注入到頁面中。
var xhr =new XMLHttpRequest();
xhr.open('get','file1.js',true);
xhr.onreadystatechange=funcition(){
if(xhr.readyState==4){
if(xhr.status>=200&&xhr.status<300||xhr.status==304){
var script=document.creat.createElement('script');
script.type="text/javascript";
script.text=xhr.responseText;
document.body.appendChild(script);
}
}
};
chr.send(null);
這段代碼發送一個GET請求獲取file.js文件。事件處理函數onreadychange檢查readyState是否爲4,同時檢驗http狀態碼是否有效(2xx表明有效響應,304表明從緩存中讀取)。若是收到了有效響應,就會建立一個<script>元素,設置該元素的text屬性爲從服務器接收到的resposeText。這樣其實是建立一個帶有內聯腳本的<script>標籤。一旦新建立的<script>元素被添加到頁面,代碼就會馬上執行而後準備就緒。
這種方法的優勢是:你能夠下載JavaScript代碼但不當即執行。因爲代碼是在<script>標籤以外返回的,所以它下載後不會自動執行,這使得你能夠把腳本執行推遲到你準備好的時候。另外一個優勢是,一樣的代碼在全部瀏覽器都能正常工做。
這種方法的侷限性是:js文件必須與所請求的頁面處於相同的域,這意味着js文件不能從cdn下載。所以,大型的web應用一般不會採用XHR腳本注入技術。
小結:
管理瀏覽器中js代碼是個棘手的問題,由於代碼執行過程會阻塞瀏覽器的其餘進程,好比用戶界面繪製。每次遇到<script>標籤,頁面都必須停下來等待全部的js代碼下載並執行,而後恢復處理。儘管如此,仍是有幾種方法能減小js對性能的影響:
1.<body>閉合標籤以前,將全部<script>標籤放在頁面底部。這能確保腳本執行前頁面已經完成渲染。
2.合併腳本。頁面中<script>標籤越少,加載也就越快,響應也更迅速。不管外鏈文件仍是內嵌的腳本都是如此。
3.有多種無阻塞下載js的方法:
---<script defer>
---使用動態建立的<script>元素來下載並執行代碼
---使用XHR對象下載js代碼並注入頁面中。