|
<html>
<head>
<title>Source Example</title>
</head>
<body>
<p>
<script type="text/javascript">
document.write("Today is " + (new Date()).toDateString());
</script>
</p>
</body>
</html>
當瀏覽器遇到<script>標籤時,當前HTML頁面無從獲知JavaScript是否會向<p> 標籤添加內容,或引入其餘元素,或甚至移除該標籤。所以,這時瀏覽器會中止處理頁面,先執行JavaScript代碼,而後再繼續解析和渲染頁面。一樣的狀況也發生在使用src屬性加載JavaScript的過程當中,瀏覽器必須先花時間下載外鏈文件中的代碼,而後解析並執行它。在這個過程當中,頁面渲染和用戶交互徹底被阻塞了。 javascript
腳本位置
HTML 4 規範指出 <script> 標籤能夠放在 HTML 文檔的<head>或<body>中,並容許出現屢次。Web 開發人員通常習慣在 <head> 中加載外鏈的 JavaScript,接着用 <link> 標籤用來加載外鏈的 CSS 文件或者其餘頁面信息。例如清單 2
<html>
<head>
<title>Source Example</title>
<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="script2.js"></script>
<script type="text/javascript" src="script3.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<p>Hello world!</p>
</body>
</html>
然而這種常規的作法卻隱藏着嚴重的性能問題。在清單 2 的示例中,當瀏覽器解析到 <script> 標籤(第 4 行)時,瀏覽器會中止解析其後的內容,而優先下載腳本文件,並執行其中的代碼,這意味着,其後的 styles.css 樣式文件和<body>標籤都沒法被加載,因爲<body>標籤沒法被加載,那麼頁面天然就沒法渲染了。所以在該JavaScript代碼徹底執行完以前,頁面都是一片空白。圖 1 描述了頁面加載過程當中腳本和樣式文件的下載過程。 css
JavaScript 文件的加載和執行阻塞其餘文件的下載 html
咱們能夠發現一個有趣的現象:第一個 JavaScript 文件開始下載,與此同時阻塞了頁面其餘文件的下載。此外,從 script1.js 下載完成到 script2.js 開始下載前存在一個延時,這段時間正好是 script1.js 文件的執行過程。每一個文件必須等到前一個文件下載並執行完成纔會開始下載。在這些文件逐個下載過程當中,用戶看到的是一片空白的頁面。
從 IE 八、Firefox 3.五、Safari 4 和 Chrome 2 開始都容許並行下載 JavaScript 文件。這是個好消息,由於<script>標籤在下載外部資源時不會阻塞其餘<script>標籤。遺憾的是,JavaScript 下載過程仍然會阻塞其餘資源的下載,好比樣式文件和圖片。儘管腳本的下載過程不會互相影響,但頁面仍然必須等待全部 JavaScript 代碼下載並執行完成才能繼續。所以,儘管最新的瀏覽器經過容許並行下載提升了性能,但問題還沒有徹底解決,腳本阻塞仍然是一個問題。
因爲腳本會阻塞頁面其餘資源的下載,所以推薦將全部<script>標籤儘量放到<body>標籤的底部,以儘可能減小對整個頁面下載的影響。例如 java
<html>
<head>
<title>Source Example</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<p>Hello world!</p>
<!-- Example of efficient script positioning -->
<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="script2.js"></script>
<script type="text/javascript" src="script3.js"></script>
</body>
</html>
這段代碼展現了在 HTML 文檔中放置<script>標籤的推薦位置。儘管腳本下載會阻塞另外一個腳本,可是頁面的大部份內容都已經下載完成並顯示給了用戶,所以頁面下載不會顯得太慢。這是優化 JavaScript 的首要規則:將腳本放在底部。
組織腳本
因爲每一個<script>標籤初始下載時都會阻塞頁面渲染,因此減小頁面包含的<script>標籤數量有助於改善這一狀況。這不只針對外鏈腳本,內嵌腳本的數量一樣也要限制。瀏覽器在解析 HTML 頁面的過程當中每遇到一個<script>標籤,都會因執行腳本而致使必定的延時,所以最小化延遲時間將會明顯改善頁面的整體性能。這個問題在處理外鏈 JavaScript 文件時略有不一樣。考慮到 HTTP 請求會帶來額外的性能開銷,所以下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減小頁面中外鏈腳本的數量將會改善性能。一般一個大型網站或應用須要依賴數個 JavaScript 文件。您能夠把多個文件合併成一個,這樣只須要引用一個<script>標籤,就能夠減小性能消耗。文件合併的工做可經過離線的打包工具或者一些實時的在線服務來實現。
須要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的<link>以後會致使頁面阻塞去等待樣式表的下載。這樣作是爲了確保內嵌腳本在執行時能得到最精確的樣式信息。所以,建議不要把內嵌腳本緊跟在<link>標籤後面。
無阻塞的腳本
減小 JavaScript 文件大小並限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上並不老是可行。Web 應用的功能越豐富,所須要的 JavaScript 代碼就越多,儘管下載單個較大的 JavaScript 文件只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。爲避免這種狀況,須要經過一些特定的技術向頁面中逐步加載 JavaScript 文件,這樣作在某種程度上來講不會阻塞瀏覽器。無阻塞腳本的祕訣在於,在頁面加載完成後才加載 JavaScript 代碼。這就意味着在 window 對象的onload事件觸發後再下載腳本。有多種方式能夠實現這一效果。
延遲加載腳本
HTML 4 爲<script>標籤訂義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,所以代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,因此它不是一個理想的跨瀏覽器解決方案。在其餘瀏覽器中,defer 屬性會被直接忽略,所以<script>標籤會以默認的方式處理,也就是說會形成阻塞。然而,若是您的目標瀏覽器支持的話,這仍然是個有用的解決方案。 瀏覽器
<script type="text/javascript" src="script1.js" defer></script> 緩存
帶有defer屬性的<script>標籤能夠放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>標籤時開始下載,但不會執行,直到 DOM 加載完成,即onload事件觸發前纔會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽器的其餘進程,所以這類文件能夠與其餘資源文件一塊兒並行下載。
任何帶有 defer 屬性的<script>元素在 DOM 完成加載以前都不會被執行,不管內嵌或者是外鏈腳本都是如此。清單 5 的例子展現了defer屬性如何影響腳本行爲:
<html>
<head>
<title>Script Defer Example</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 屬性的瀏覽器的彈出順序是:「defer」、「script」、「load」。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:「script」、「defer」、「load」。請注意,帶有 defer 屬性的<script>元素不是跟在第二個後面執行,而是在 onload 事件被觸發前被調用。
若是您的目標瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那麼 defer 腳本確實有用。若是您須要支持跨領域的多種瀏覽器,那麼還有更一致的實現方式。
HTML 5 爲<script>標籤訂義了一個新的擴展屬性:async。它的做用和 defer 同樣,可以異步地加載和執行腳本,不由於加載腳本而阻塞頁面的加載。可是有一點須要注意,在有 async 的狀況下,JavaScript 腳本一旦下載好了就會執行,因此頗有可能不是按照本來的順序來執行的。若是 JavaScript 腳本先後有依賴性,使用 async 就頗有可能出現錯誤。
動態腳本元素
文檔對象模型(DOM)容許您使用 JavaScript 動態建立 HTML 的幾乎所有文檔內容。<script>元素與頁面其餘元素同樣,能夠很是容易地經過標準 DOM 函數建立: 安全
var script = document.createElement ("script");
script.type = "text/javascript";
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
新的<script>元素加載 script1.js 源文件。此文件當元素添加到頁面以後馬上開始下載。此技術的重點在於:不管在何處啓動下載,文件的下載和運行都不會阻塞其餘頁面處理過程。您甚至能夠將這些代碼放在<head>部分而不會對其他部分的頁面代碼形成影響(除了用於下載文件的 HTTP 鏈接)。
當文件使用動態腳本節點下載時,返回的代碼一般當即執行(除了 Firefox 和 Opera,他們將等待此前的全部動態腳本節點執行完畢)。當腳本是「自運行」類型時,這一機制運行正常,可是若是腳本只包含供頁面其餘腳本調用調用的接口,則會帶來問題。這種狀況下,您須要跟蹤腳本下載完成並是否準備妥善。可使用動態 <script> 節點發出事件獲得相關信息。
Firefox、Opera, Chorme 和 Safari 3+會在<script>節點接收完成以後發出一個 onload 事件。您能夠監聽這一事件,以獲得腳本準備好的通知: 服務器
var script = document.createElement ("script")
script.type = "text/javascript";
//Firefox, Opera, Chrome, Safari 3+
script.onload = function(){
alert("Script loaded!");
};
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
Internet Explorer 支持另外一種實現方式,它發出一個 readystatechange 事件。<script>元素有一個readyState 屬性,它的值隨着下載外部文件的過程而改變。readyState 有五種取值: 網絡
經過檢查readyState狀態加載JavaScript腳本:
app
var script = document.createElement("script")
script.type = "text/javascript";
//Internet Explorer
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
alert("Script loaded.");
}
};
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
大多數狀況下,您但願調用一個函數就能夠實現JavaScript文件的動態加載。下面的函數封裝了標準實現和 IE 實現所需的功能:
經過函數進行封裝
JS代碼
經過函數進行封裝
JS代碼
loadScript()函數使用方法
JS代碼
經過 loadScript()函數加載多個JavaScript腳本
JS代碼
經過 XHR 對象加載 JavaScript 腳本
JS代碼