高性能的JavaScript--加載和執行

寫在前面

JavaScript在瀏覽器中的性能,可認爲是開發者所要面對的最重要的可用性的問題,此問題因JavaScript的阻塞特徵而複雜,也就是說JavaScript運行時其餘的事情不能被瀏覽器處理,事實上,大多數瀏覽器使用單進程處理UI更新和JavaScript運行等多個任務,而同一時間只能有一個任務被執行。JavaScript運行了多長時間,那麼瀏覽器空閒下來響應用戶輸入以前的等待時間就有多長。javascript

從基本層面說,這就意味着<script>標籤的出現使整個頁面因腳本解析、運行出現等待。不論實際的JavaScript代碼是內聯的仍是包含在一個不相干的外部文件中頁面下載和解析過程必須停下,等待腳本完成這些處理,而後才能繼續,也是頁面生命週期必不可少的部分,由於腳本可能在運行過程當中修改頁面內容。css

在加載JavaScript過程當中,頁面解析和用戶交互是被徹底阻塞的。html

腳本位置

 HTML 4 文檔指出,一個<script>標籤能夠放在 HTML文檔的 <head> 或者<body>標籤中,能夠在其中屢次出現。傳統上,<script> 標籤用於加載外部JavaScript 文件。<head>部分除此類代碼外,還包含 <link>標籤用於加載外部css文件和其餘頁面中間件。也就是說,最好把風格和行爲所依賴的部分放在一塊兒,首先加載他們,使他們能夠獲得正確的外觀和行爲。java

例如:瀏覽器

 1 <html> 
 2 <head>
 3 <title>Script Example</title>
 4 <-- Example of ineffi cient script positioning -->
 5 <script type="text/javascript" src="file1.js"></script>
 6 <script type="text/javascript" src="file2.js"></script>
 7 <script type="text/javascript" src="file3.js"></script>
 8 <link rel="stylesheet" type="text/css" href="styles.css">
 9 </head>
10 <body>
11 <p>Hello world!</p>
12 </body>
13 </html>


雖然這些代碼看起來沒什麼問題,可是在〈head〉部分加載了三個JavaScript文件。每一個〈script〉標籤阻塞了頁面解析過程,直到完整的下載並運行了外部JavaScript代碼以後,頁面才能繼續進行。在瀏覽器沒有遇到〈body〉標籤以前,不會渲染頁面的任何部分。緩存

把腳本放在頁面的頂端,將會致使一個能夠察覺的延遲,一般表現爲:頁面打開一片白,用戶不能閱讀和操做。服務器

如圖,當第一javas文件開始下載時,阻塞了其餘文件下載。進一步當第一個文件下載完成以後和第二個文件下載以前有一個延時,是第一個文件徹底運行所須要的時間。網絡

解決這個問題推薦的辦法是:將全部<script> 標籤放在儘量接近<body> 標籤的底部位置,儘可能減小對整個頁面下載的影響。app

如:dom

 1 <html>
 2 <head>
 3 <title>Script Example</title>
 4 <link rel="stylesheet" type="text/css" href="styles.css">
 5 </head>
 6 <body>
 7 <p>Hello world!</p>
 8 <-- Example of recommended script positioning -->
 9 <script type="text/javascript" src="file1.js"></script>
10 <script type="text/javascript" src="file2.js"></script>
11 <script type="text/javascript" src="file3.js"></script>
12 </body>
13 </html>
組成腳本

 因爲每一個<script>標籤下載時阻塞頁面解析過程,因此限制頁面的<script>總數也是能夠改善性能。這個規則對內聯腳本和外部腳本一樣適用。每當頁面解析碰到一個<script>標籤時,緊接着有一段時間用於代碼執行。最小化這些延遲時間能夠改善頁面的總體性能。

每一個HTTP請求都會產生額外的性能負擔,下載一個100KB的文件比下載4個25KB的文件要快。總之,減小引用外部文件的數量。典型的,一個大型網站或者網頁應用須要屢次請求JavaScript文件。你能夠將這些文件整合成一個文件,只須要一個<script>標籤引用,就能夠減小性能損失。

非阻塞腳本

 JavaScript傾向於阻塞瀏覽器某些處理過程,如HTTP請求和界面刷新,這是開發者面臨的最顯著的性能問題。保持JavaScript文件短小,並限制HTTP請求的數量,只是建立反應迅速的網頁應用的第一步。一個應用程序所包含的功能越多,所須要的JavaScript代碼就越大,保持源碼短小並不老是一種選擇。儘量下載一個大JavaScript文件只產生一次HTTP請求。卻會鎖住瀏覽器一大段時間。爲避開這種狀況,你須要向頁面中逐步添加JavaScript,某種程度上說不會阻塞瀏覽器。

非阻塞腳本的祕密在於,等頁面加載以後,再加載JavaScript源碼。從技術角度上講,這意味着在window的load事件發出以後下載代碼。有幾種方法能夠實現這種效果。

1.延期腳本

HTML4爲<script>標籤訂義了一個擴展屬性:defer。這個defer屬性指明元素中所包含的腳本不打算修改DOM,所以代碼能夠稍後執行(適用於IE4以上瀏覽器)

<script type="text/javascript" src="file1.js" defer></script>

帶有該屬性的JavaScript文件在<script>被解析時啓動下載,但代碼不會被執行,直到DOM加載完成,它不會阻塞瀏覽器的其餘處理過程,因此這些文件能夠與頁面的其餘資源一塊兒並行下載。
2.動態腳本元素

文檔對象模型dom容許使用JavaScript動態建立HTML的幾乎所有文檔內容。其根本在於<script>元素與頁面其餘元素沒有什麼不一樣。
 當文件使用動態腳本節點下載時,返回的代碼一般當即執行。當腳本「自運行」類型時這一機制運行正常,可是若是腳本只包含頁面其餘腳本調用的的接口,則會帶來問題。這種狀況下,你須要跟蹤腳本下載完成並準備妥善的狀況。
IE 會發出一個readystatechange事件。<script>元素有一個readyState屬性,它的值隨着外部下載的過程而改變。readyState有5種取值。
uninitialized       默認狀態
loading             開始下載
interactive        下載完成但尚不可用
complete          全部數據都已經準備好

下面封裝一個函數來實現JavaScript文件的動態加載:

 1 function loadScript (url, callback){
 2 var script = document.createElement ("script")
 3 script.type = "text/javascript";
 4 if (script.readyState){ //IE
 5 script.onreadystatechange = function(){
 6 if (script.readyState == "loaded" ||  script.readyState == "complete"){
 7 script.onreadystatechange = null;
 8 callback();
 9 }
10 };
11 } else {  //Others
12 script.onload = function(){
13 callback();
14 };
15 }
16 script.src = url;
17 document.getElementsByTagName_r("head")[0].appendChild(script);
18 }

使用方法:

1 loadScript("file1.js", function(){
2 alert("File is loaded!");
3 }); 

使文件按順序加載:

1 loadScript("file1.js", function(){
2 loadScript("file2.js", function(){
3 loadScript("file3.js", function(){
4 alert("All files are loaded!");
5 });
6 });
7 });

3.XHR腳本注入
使用XMLHttpRequest(XHR)對象將腳本注入到頁面中。此技術首先建立一個XHR對象,而後下載javas文件,接着用一個動態<script>元素將javas代碼注入頁面。

 1 var xhr = new XMLHttpRequest();
 2 xhr.open("get", "file1.js", true);
 3 xhr.onreadystatechange = function(){
 4 if (xhr.readyState == 4){
 5 if (xhr.status >= 200 && xhr.status < 300 | | xhr.status == 304){
 6 var script = document.createElement ("script");
 7 script.type = "text/javascript";
 8 script.text = xhr.responseText;
 9 document.body.appendChild(script);
10 }
11 }
12 };
13 xhr.send(null);

此代碼向服務器發送一個獲取file1.js文件的GET請求。onreadystatechange事件處理函數檢查readyState是否是4,而後檢查HTTP狀態碼是否是有效(2XX表示有效迴應,304表示一個緩存響應)。若是收到一個有效的響應,那麼就建立一個新的<script>元素,將它的文本屬性設置爲從服務器接受到的resposeText字符串。這樣作實際上會建立一個帶有內聯代碼的<script>元素。一旦新的<script>元素被添加到文檔,代碼將被執行並準備使用。

這種方法的主要優勢是,您能夠下載不當即執行的 JavaScript 代碼。因爲代碼返回在<script>標籤以外(換句話說不受<script>標籤約束),它下載後不會自動執行,這使得您能夠推遲執行,直到一切都準備好了。另外一個優勢是,一樣的代碼在全部現代瀏覽器中都不會引起異常。 

此方法最主要的限制是:JavaScript 文件必須與頁面放置在同一個域內,不能從 CDN 下載(CDN 指」內容投遞網絡(Content Delivery Network)」,因此大型網頁一般不採用 XHR 腳本注入技術。

總結

減小 JavaScript 對性能的影響有如下幾種方法:

  • 將全部的<script>標籤放到頁面底部,也就是</body>閉合標籤以前,這能確保在腳本執行前頁面已經完成了渲染。
  • 儘量地合併腳本。頁面中的<script>標籤越少,加載也就越快,響應也越迅速。不管是外鏈腳本仍是內嵌腳本都是如此。
  • 採用無阻塞下載 JavaScript 腳本的方法:
  • 使用<script>標籤的 defer 屬性(僅適用於 IE 和 Firefox 3.5 以上版本);
  • 使用動態建立的<script>元素來下載並執行代碼;
  • 使用 XHR 對象下載 JavaScript 代碼並注入頁面中。
相關文章
相關標籤/搜索