JS具有阻塞特性,當瀏覽器在執行js代碼時,不能同時做其它事情,即<script>每次出現都會讓頁面等待腳本的解析和執行(不論JS是內嵌的還是外鏈的),JS代碼執行完成后,才繼續渲染頁面。
由于,JS的這種阻塞特性,每次遇到<script>,頁面都必須停下來等待腳本下載并執行,這會停止頁面繪制,帶來不好的用戶體驗。所以,有必要減少JS阻塞特性造成的困擾。
1 優化腳本位置
HTML4規范中,<script>可以放在<head>或<body>中。你可能習慣性的在<head>中放置多個外鏈JS、CSS,以求優先加載它們。瀏覽器在繼續到<body>之前,不會渲染頁面,所以,把JS放在<head>中,會導致延遲。為了提高用戶體驗,新一代瀏覽器都支持并行下載JS,但是JS下載仍然會阻塞其它資源的下載(eg.圖片)。盡管腳本的下載過程并不會相互影響,但頁面仍然必須等待所有JS下載并執行完成才能繼續。顯見,所有<script>應該盡可能放到<body>的底部,以減少對頁面下載的影響。
注意:CSS文件本身是并行下載,不會阻塞頁面的其他進程。但是,如果把一段內嵌腳本放在引用外鏈CSS的<link>之后會導致頁面阻塞去等待CSS的下載。這樣做是為了確保內嵌腳本在執行時能夠獲得正確的樣式信息。所以,最好不要把內嵌腳本放在CSS的<link>之后。
2 減少外鏈腳本數量以改善性能
原因很簡單,額外的HTTP請求會帶來額外的開銷,所以減少頁面中外鏈腳本的數量,有助于改善性能。
3 使用無阻塞下載JS方法
無阻塞腳本的秘訣在于,在頁面加載完成后才加載JS,即在window對象的load事件觸發后在下載腳本。
3.1 使用<script>的defer屬性(僅IE和Firefox3.5以上);
defer屬性指明本元素所含的腳本不會修改DOM,因此代碼能安全的延遲執行。defer屬性的<script>,對應的JS文件將在頁面解析到<script>時開始下載,但并不會執行,直到DOM加載完成,即onload事件觸發前被調用。當一個帶有defer屬性的JS文件下載時,他不會阻塞瀏覽器的其它進程,因此這類文件可以與頁面中的其他資源并行下載。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DeferredScripts</title>
</head>
<body>
<script type="text/javascript" defer>
alert("defer");
</script>
<script type="text/javascript">
alert("script");
</script>
<script type="text/javascript">
window.onload = function() {
alert("load");
};
</script>
</body>
</html>
對于支持defer的瀏覽器彈出順序是:script>defer>load;而不支持該屬性的瀏覽器的彈出順序為:defer>script>load。
3.2 使用動態創建的<script>元素來下載并執行代碼
實例代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DynamicScriptElements</title>
</head>
<body>
<script type="text/javascript">
function loadScript(url, callback) {
var script = document.createElement("Script");
script.type = "text/javascript";
//IE 驗證腳本是否下載完成
if (script.readyState) {
script.onreadystatechange = function() {
//readyState屬性有5種取值
//uninitialized:初始狀態
//loading:開始下載
//interactive:數據完成下載但尚不可用
//complete:數據已經準備就緒
//實際使用時,readyState的值并不像我們預想的那樣有規律,實踐發現使用readyState
//最靠譜的方式是同時檢查以下2個狀態,只要其中1個觸發,就認為腳本下載完成。
if (script.readyState == "loaded" || script.readyState == "complete") {
//移除事件處理器,確保事件不會處理2次
script.onreadystatechange = null;
callback();
}
};
}
//其他瀏覽器
else {
script.onload = function() {
callback();
};
}
script.src = url;
//把新建的<Script>添加到<head>里比添加到<body>里更保險。
document.getElementsByTagName("head")[0].appendChild(script);
}
//動態加載多個JS文件
//優先加載Common.js,等待Common.js加載完畢后加載Costom.js
//不同瀏覽器的執行順序不同
//Firefox、Opera能夠保證按照你腳本的加載順序來執行
//其他瀏覽器會按照從服務端返回的順序執行代碼,因此使用嵌套的方法保證調用順序
loadScript("Common.js", function() {
loadScript("Costom.js", function() {
alert("all load");
});
});
</script>
</body>
</html>
文件在該元素被添加到頁面時開始下載。這種技術的重點在于:無論在何時啟動下載,文件的下載與執行不會阻塞頁面的其他進程。使用動態腳本節點下載文件時,根據瀏覽器不同,多數瀏覽器,返回的代碼會立即執行(Firefox、Opera,會等待此前所有動態節點執行完畢)。當腳本”自執行“時,這種機制運行正常,但是當代碼內只包含供其它腳本調用的接口時,就必須確保腳本下載完成并準備就緒,在上例中列舉了不同瀏覽器的驗證方法。
注意:如果多個文件的順序很重要,更好的做法是把它們按正確順序合并為一個文件。此外,說把新建的<Script>添加到<head>里比添加到<body>里更保險是因為要盡量避免頁面報錯(在低版本的IE中使用不當會發生"操作已中止"錯誤。
3.3 使用XHR對象下載JS代碼并注入頁面中
實例代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XhrScriptInjection</title>
</head>
<body>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("get", "JScript.js", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
//2XX表示有效響應,304表示從緩存讀取
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
//創建內嵌腳本
var script = document.createElement("script");
script.type = "text/javascript";
script.text = xhr.responseText;
document.body.appendChild(script); //一旦新創建的<script>被添加到頁面,代碼就立刻執行然后準備就緒。
}
}
};
xhr.send(null);
</script>
</body>
</html>
這種方法的優點是,你可以下載JS代碼但不立即執行。由于代碼是在<script>標簽之外返回的,因此它下載后不會自動執行,這使得你可以把腳本的執行推遲到你準備好的時候。另一個優點是,同樣的代碼在所有主流瀏覽器中都能正常工作。
這種方法的主要局限性是JS文件必須與所有請求的頁面處于相同的域。
綜上所述,向頁面中添加大量JS的推薦做法只需兩步:先添加動態加載的所需代碼,然后加載初始化頁面所需的剩下代碼。
<script type="text/javascript" src="Common.js"></script>
<script type="text/javascript">
loadScript("Costom.js", function() {
//Do Something
});
</script>
優化前:
優化后:
"操作已中止"錯誤<html>
<head>
<title>Operation Aborted Example</title>
</head>
<body>
<p>The following code should cause an Operation Aborted error in IE versions prior to 8.</p>
<div>
<script type="text/javascript">
document.body.appendChild(document.createElement("div"));
</script>
</div>
</body>
</html>上述代碼在低版本IE中會報"操作已中止"錯誤。出現此問題的原因是子容器 HTML 元素包含試圖修改父容器元素的子容器的腳本。腳本試圖使用 innerHTML 方法或 appendChild 方法修改父容器元素。例如對于如果 DIV 元素是一個 BODY 元素中的子容器,并且在 DIV 元素中的一個 SCRIPT 塊試圖修改 DIV 元素的父容器的 BODY 元素可能會出現此問題。
最簡單的解決方法:將腳本移到body元素的范圍。
<html>
<head>
<title>Operation Aborted Example</title>
</head>
<body>
<p>The following code should cause an Operation Aborted error in IE versions prior to 8.</p>
<div>
</div>
<script type="text/javascript">
document.body.appendChild(document.createElement("div"));
</script>
</body>
</html>其它解決方法可以參考:
http://www.nczonline.net/blog/2008/03/17/the-dreaded-operation-aborted-error