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./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www./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./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www./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./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www./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./blog/2008/03/17/the-dreaded-operation-aborted-error http://support.microsoft.com/kb/927917/zh-cn
|