從上圖可以看出,當浏覽器遇到<script>标簽時,浏覽器會停止處理頁面,先執行Javascript代碼,然後再繼續解析和渲染頁面。在這個過程中,頁面和使用者的互動完全被阻塞了。通常表現為顯示空白頁面,使用者無法浏覽内容。
現在的浏覽器都支援并行下載下傳檔案,比如上圖同時下載下傳多個圖檔。而且,IE8 、FireFox3.5、Safari4和Chrome2都支援并行下載下傳javascript檔案,但是javascript檔案的下載下傳會阻塞其他資源的下載下傳。盡管腳本的下載下傳過程不會互相影響,但是頁面仍然需要等待javascript的執行完畢才能繼續。是以,盡管最新的浏覽器通過并行下載下傳提高了性能,但是腳本阻塞問題仍然沒有解決。
解決辦法有:
1. 把<script>标簽放到</body>閉合之前
由于腳本會阻塞其他資源的下載下傳,是以推薦把所有的<script>标簽盡可能的放到<body>标簽的底部,以盡量減少對整個頁面下載下傳的影響。這是雅虎性能小組提出的優化JavaScript的首要規則:把腳本放在底部。
2. 腳本合并,減少<script>标簽的數量
每次遇到<script>标簽,浏覽器都要發一次HTTP請求。而HTTP請求耗時是web性能的最大的影響之一。
3. 腳本延遲加載
HTML 4為<script>标簽定義了一個擴充屬性:defer。defer 屬性規定當頁面已完成加載後,才會執行腳本。目前所有主流浏覽器都支援defer。注意:defer 屬性僅适用于外部腳本(隻有在使用 src 屬性時)。
一個帶有defer屬性的<script>标簽可以放置在文檔的任何位置,它會在被解析時啟動下載下傳,直到DOM加載完成(在onload事件句柄被調用之前)。當一個defer的Javascript檔案被下載下傳時,它不會阻塞浏覽器的其他處理過程,是以這些檔案可以與其他資源一起并行下載下傳。
除了defer屬性外,HTML 5 規範中引入了async屬性,用于異步加載腳本。async與defer的相同點是采用并行下載下傳,在下載下傳過程中不會産生阻塞。差別在于執行時機,async是加載完成後自動執行,而defer需要等待頁面完成後執行。這就會造成腳本的執行順序和頁面上腳本的排放順序不一緻,可能造成腳本依賴的問題(指async)。
4. 動态腳本加載
DOM允許我們使用Javascript動态建立HTML的幾乎所有文檔内容,一個新的<script>元素可以非常容易的通過标準DOM建立:
var script=document.createElemetn("script");
script.type="text/javascript";
script.src="file.js";;
document.getElementsByTagName("head").appendChild(script);
新的<script>元素加載file1.js源檔案。此檔案當元素添加到頁面後立刻開始下載下傳。此技術的重點在于:無論在何處啟動下載下傳,檔案的下載下傳和運作都不會阻塞其他頁面處理過程。
當檔案使用動态腳本節點下載下傳時,傳回的代碼通常立即執行(除了Firefox和Opera,它們将等待此前的所有動态腳本節點執行完畢)。
大多數情況下,我們希望調用一個函數就可以實作Javascript檔案的動态下載下傳。下面的函數封裝實作了标準實作和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 { //Others
script.onload = function(){ callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("file1.js", function(){ //調用
alert("File is loaded!");
});
如果需要加載多個js檔案,一定要考慮清楚檔案的加載順序。你可以将下載下傳操作串聯起來以確定下載下傳順序:
loadScript("file1.js",function(){
loadScript("file2.js",function(){
loadScript("file3.js",function(){
alert("ok");
});
});
});
此函數接受兩個參數:Javascript檔案的Url和一個當Javascript接收完成時觸發的回調函數。屬性檢查用于決定監視哪種事件。最後一步src屬性,并将javascript檔案添加到head。
動态腳本加載憑借它在跨浏覽器相容性和易用的優勢,成為最通用的無阻塞加載解決方案。