<html> <head> <title>Script Example</title> </head> <body> <p> <script type="text/javascript"> document.write("The date is " + (new Date()).toDateString()); </script> </p> </body> </html>
當瀏覽器遇到一個<script>標籤時,正如上面HTML頁面中那樣,沒法預知JavaScript是否在<p>標籤中添加內容。所以,瀏覽器停下來,運行此JavaScript代碼,而後再繼續解析、翻譯頁面。一樣的事情發生在使用src屬性加載JavaScript的過程當中。瀏覽器必須首先下載外部文件的代碼,這要佔用一些時間,而後解析並運行此代碼。此過程當中,頁面解析和用戶交互是被徹底阻塞的。javascript
爲了保持代碼的類似性,咱們儘可能將相同的代碼組織在一塊兒,例如:css
<html> <head> <title>Script Example</title> <-- Example of inefficient script positioning --> <script type="text/javascript" src="file1.js"></script> <script type="text/javascript" src="file2.js"></script> <script type="text/javascript" src="file3.js"></script> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <p>Hello world!</p> </body> </html>
這些看起來比較規整的代碼有一個明顯的性能問題就是:在<head>部分加載了三個JavaScript文件。由於每一個<script>標籤阻塞了頁面的解析過程,直到它完整地下載並運行了外部JavaScript代碼以後,頁面處理才能繼續進行。用戶必須忍受這種能夠察覺的延遲。請記住,瀏覽器在遇到<body>標籤以前,不會渲染頁面的任何部分。用這種方法把腳本放在頁面的頂端,將致使一個能夠察覺的延遲,一般表現爲:頁面打開時,首先顯示爲一幅空白的頁面,而此時用戶即不能閱讀,也不能與頁面進行交互操做。爲了更好地理解此過程,咱們使用瀑布圖來描繪每一個資源的下載過程。圖1-1顯示出頁面加載過程當中,每一個腳本文件和樣式表文件下載的過程。html
第一個JavaScript文件開始下載,並阻塞了其餘文件的下載過程。進一步,在file1.js下載完以後和file2.js開始下載以前有一個延時,這是file1.js徹底運行所需的時間。每一個
文件必須等待前一個文件下載完成並運行完以後,才能開始本身的下載過程。當這些文件下載時,用戶面對一個空白的屏幕。這就是今天大多數瀏覽器的行爲模式。Internet Explorer 8, Firefox 3.5, Safari 4, 和Chrome 2容許並行下載JavaScript文件。這個好消息代表,當一個<script>標籤正在下載外部資源時,沒必要阻塞其餘<script>標籤。不幸的是,JavaScript的下載仍然要阻塞其餘資源的下載過程,例如圖片。即便腳本之間的下載過程互不阻塞,頁面仍舊要等待全部JavaScript代碼下載並執行完成以後才能繼續。因此,當瀏覽器經過容許並行下載提升性能以後,該問題並無徹底解決。由於腳本阻塞其餘頁面資源的下載過程,因此推薦的辦法是:將全部<script>標籤放在儘量接近<body>標籤底部的位置,儘可能減小對整個頁面下載的影響。例如java
<html> <head> <title>Script Example</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <p>Hello world!</p> <-- Example of recommended script positioning --> <script type="text/javascript" src="file1.js"></script> <script type="text/javascript" src="file2.js"></script> <script type="text/javascript" src="file3.js"></script> </body> </html>
此代碼展現了所推薦的<script>標籤在HTML文件中的位置。儘管腳本下載之間互相阻塞,但頁面已經下載完成而且顯示在用戶面前了,進入頁面的速度不會顯得太慢。這正是「Yahoo! 優越性能小組」關於JavaScript的第一條定律:將腳本放在底部。git
另外,瀏覽器請求4個25K的外部JS文件的速度要慢於請求1個100K的js文件,所以,咱們應該儘可能將JS文件包含在一箇中加載。(每一個HTTP請求都會產生額外的性能負擔,下載
一個100KB的文件比下載四個25KB的文件要快)。github
Deferred Scripts 延期腳本shell
HTML 4爲<script>標籤訂義了一個擴展屬性:defer。這個defer屬性指明元素中所包含的腳本不打算修改DOM,所以代碼能夠稍後執行。defer屬性只被Internet Explorer 4和Firefox 3.5更高版本的瀏覽器所支持,它不是一個理想的跨瀏覽器解決方案。在其餘瀏覽器上,defer屬性被忽略,<script>標籤按照默認方式被處理(形成阻塞)。若是瀏覽器支持的話,這種方法還是一種有用的解決方案。示例以下:瀏覽器
<script type="text/javascript" src="file1.js" defer></script>緩存
一個帶有defer屬性的<script>標籤能夠放置在文檔的任何位置。對應的JavaScript文件將在<script>被解時啓動下載,但代碼不會被執行,直到DOM加載完成(在onload事件句柄被調用以前)。當一個defer的JavaScript文件被下載時,它不會阻塞瀏覽器的其餘處理過程,因此這些文件能夠與頁面的其餘資源一塊兒並行下載.(須要理解的是一個<script>腳本的執行時間包含兩部分,(1)請求加載時間(2)JS執行時間。defer的做用是延遲JS的執行時間。任何帶有defer屬性的<script>元素在DOM加載完成以前不會被執行,不管是內聯腳本仍是外部腳本文件,都是這樣。下面的例子展現了defer屬性如何影響腳本行爲。服務器
<html> <head> <title>Script Defer Example</title> </head> <body> <script defer> alert("defer"); </script> <script> alert("script"); </script> <script> window.onload = function(){ alert("load"); }; </script> </body> </html>
這些代碼在頁面處理過程當中彈出三個對話框。若是瀏覽器不支持defer屬性,那麼彈出對話框的順序是「defer」,「script」和「load」。若是瀏覽器支持defer屬性,那麼彈出對話框的順序是「script」,「defer」和「load」。注意,標記爲defer的<script>元素不是跟在第二個後面運行,而是在onload事件句柄處理以前被調用。
Dynamic Script Elements 動態腳本元素
文檔對象模型(DOM)容許你使用JavaScript動態建立HTML的幾乎所有文檔內容。其根本在於,<script>元素與頁面其餘元素沒有什麼不一樣:引用變量能夠經過DOM進行檢索,能夠從文檔中移動、刪除,也能夠被建立。一個新的<script>元素能夠很是容易地經過標準DOM函數建立
var script = document.createElement ("script"); script.type = "text/javascript"; script.src = "file1.js"; document.getElementsByTagName_r("head")[0].appendChild(script);
新的<script>元素加載file1.js源文件。此文件當元素添加到頁面以後馬上開始下載。此技術的重點在於:不管在何處啓動下載,文件的下載和運行都不會阻塞其餘頁面處理過程。
你甚至能夠將這些代碼放在<head>部分而不會對其他部分的頁面代碼形成影響(除了用於下載文件的HTTP鏈接)
大多數狀況下,你但願調用一個函數就能夠實現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_r("head")[0].appendChild(script); }
另外一個以非阻塞方式得到腳本的方法是使用XMLHttpRequest(XHR)對象將腳本注入到頁面中。此技術首先建立一個XHR對象,而後下載JavaScript文件,接着用一個動態<script>元素將JavaScript代碼注入頁面。下面是一個簡單的例子:
var xhr = new XMLHttpRequest(); xhr.open("get", "file1.js", true); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ 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); } } }; xhr.send(null);
此代碼向服務器發送一個獲取file1.js文件的GET請求。onreadystatechange事件處理函數檢查readyState是否是4,而後檢查HTTP狀態碼是否是有效(2XX表示有效的迴應,304表示一個緩存響應)。若是收到了一個有效的響應,那麼就建立一個新的<script>元素,將它的文本屬性設置爲從服務器接收到的responseText字符串。這樣作實際上會建立一個帶有內聯代碼的<script>元素。一旦新<script>元素被添加到文檔,代碼將被執行,並準備使用。
這種方法的主要優勢是,你能夠下載不當即執行的JavaScript代碼。因爲代碼返回在<script>標籤以外(換句話說不受<script>標籤約束),它下載後不會自動執行,這使得你能夠推遲執行,直到一切都準備好了。另外一個優勢是,一樣的代碼在全部現代瀏覽器中都不會引起異常。此方法最主要的限制是:JavaScript文件必須與頁面放置在同一個域內,不能從CDNs下載(CDN指「內容投遞網絡(Content Delivery Network)」,前面002篇《成組腳本》一節提到)。正由於這個緣由,大型網頁一般不採用XHR腳本注入技術。
推薦的向頁面加載大量JavaScript的方法分爲兩個步驟:第一步,包含動態加載JavaScript所需的代碼,而後加載頁面初始化所需的除JavaScript以外的部分。這部分代碼儘可能小,可能只包含loadScript()函數,它下載和運行很是迅速,不會對頁面形成很大幹擾。當初始代碼準備好以後,用它來加載其他的JavaScript。
例如:
<script type="text/javascript" src="loader.js"></script> <script type="text/javascript"> loadScript("the-rest.js", function(){ Application.init(); }); </script>
將此代碼放置在body的關閉標籤</body>以前。這樣作有幾點好處:首先,像前面討論過的那樣,這樣作確保JavaScript運行不會影響頁面其餘部分顯示。其次,當第二部分JavaScript文件完成下載,全部應用程序所必須的DOM已經建立好了,並作好被訪問的準備,避免使用額外的事件處理(例如window.onload)來得知頁面是否已經準備好了
做爲一個更通用的工具,Yahoo! Search的Ryan Grove建立了LazyLoad庫(參見http://github.com/rgrove/lazyload/)。LazyLoad是一個更強大的loadScript()函數。LazyLoad精縮以後只有大約1.5KB(精縮,而不是用gzip壓縮的)。具體用法再也不贅述,請參考詳細文檔說明。
另外一個非阻塞JavaScript加載庫是LABjs(http://labjs.com/),Kyle Simpson寫的一個開源庫,由Steve Souders贊助。此庫對加載過程進行更精細的控制,並嘗試並行下載儘量多的代碼。LABjs也至關小,只有4.50KB(精縮,而不是用gzip壓縮的),因此具備最小的頁面代碼尺寸
管理瀏覽器中的JavaScript代碼是個棘手的問題,由於代碼執行阻塞了其餘瀏覽器處理過程,諸如用界面繪製。每次遇到<script>標籤,頁面必須停下來等待代碼下載(若是是外部的)並執行,而後再繼續處理頁面其餘部分。可是,有幾種方法能夠減小JavaScript對性能的影響:
[1] 將全部<script>標籤放置在頁面的底部,緊靠body關閉標籤</body>的上方。此法能夠保證頁面在腳本運行以前完成解析。
[2] 將腳本成組打包。頁面的<script>標籤越少,頁面的加載速度就越快,響應也更加迅速。不論外部腳本文件仍是內聯代碼都是如此.
[3] 有幾種方法可使用非阻塞方式下載JavaScript:
——爲<script>標籤添加defer屬性(只適用於Internet Explorer和Firefox 3.5以上版本)
——動態建立<script>元素,用它下載並執行代碼
——用XHR對象下載代碼,並注入到頁面中