高性能JavaScript之加載和執行

JS在瀏覽器中的性能,能夠認爲是開發者所面臨的最重要的可行性問題。這個問題因JS的阻塞特性變得複雜,也就是說當瀏覽器在執行JS代碼時,不能同時作其餘任何事情。事實上,大多數瀏覽器都使用單一進程來處理UI(用戶界面)更新和JavaScript腳本執行,因此同一時刻只能作其中一件事情。JS執行過程耗時越久,瀏覽器等待響應用戶輸入的時間就越長。css

 

從基礎層面來講,這意味着<script>標籤每次出現都霸道地讓頁面等待腳本的解析和執行。不管當前的JS代碼是內嵌的仍是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本的執行完成。這在頁面生存週期中是必要的,由於腳本執行過程當中可能會修改頁面的內容。一個典型的例子就是在頁面中使用document.write()(常常用來顯示廣告)。html

例如:webpack

<html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>Script Example</title>
       
    </head>

    <body>
      <p>
      <script>
      document.write("The date is "+(new Date()).toDateString());
      </script>
      </p>
    </body>
</html>

 

當瀏覽器遇到<script>標籤時,當前的HTML頁面無從獲知JS是否會向<p>標籤添加內容,或引入其餘元素,或關閉該標籤。所以,這時瀏覽器會停滯處理頁面,先執行JS代碼,而後再繼續解析和渲染頁面。一樣的狀況也發生在使用src的屬性加載JS的過程當中,瀏覽器必須先花時間下載外鏈文件中的代碼,而後解析並執行它。在這個過程當中,頁面渲染和用戶交互徹底被阻塞了。web

 

1.1腳本位置gulp

這裏先說說HTML4規範,HTML4規範指出<script>標籤能夠放在HTML文檔的<head>或<body>中,並容許出現屢次。按照慣例,<script>標籤用來加載出如今<head>中的外鏈JS文件中,挨着的<link>標籤用來加載外部CSS文件或其餘頁面元信息。也就是說,把與樣式和行爲有關的腳本放在一塊兒,並先加載它們,使得頁面可以顯示正確的外觀和交互。瀏覽器

例如:緩存

<html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>Script Example</title>
        <script src="file1.js"></script>
        <script src="file2.js"></script>
        <script src="file3.js"></script>
        <link rel="stylesheet" type="text/css" href="style.css">
       
    </head>

    <body>
      <p>
Hello World
      </p>
    </body>
</html>

 

這些看似正常的代碼實際上有十分嚴重的性能問題:在<head>中加載了三個JS文件。因爲腳本會阻塞頁面的渲染,直到它們所有下載並執行完成後,頁面的渲染纔會繼續。安全

所以頁面的性能問題會很明顯。請記住,瀏覽器在解析到<body>標籤以前,不會渲染頁面的任何部分。把腳本放到頁面頂部將會致使明顯的延遲,一般表現爲顯示空白頁面,用戶沒法瀏覽內容,也沒法與頁面進行交互。函數

因此一般建議像JS腳本通常都放在</body>前,也就是頁面最底下,而CSS文件放在<head></head>之間。雖說CSS文件過大也會致使延遲,可是這種延遲是能夠接受的,若是是JS腳本與CSS腳本放在<head></head>之間如上面的代碼所示,那樣的話,延遲會顯得十分明顯。所以推薦<script>標籤儘量放到<body>標籤底部,</body>標籤以前,以儘可能減小對整個頁面下載的影響。性能

例如:

<html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>Script Example</title>
        <link rel="stylesheet" type="text/css" href="style.css">
       
    </head>

    <body>
      <p>
Hello World
      </p>

        <script src="file1.js"></script>
        <script src="file2.js"></script>
        <script src="file3.js"></script>
    </body>
</html>

 

記得在《高性能網站建設》這本書,其中提到的建議之一:就是將腳本放在底部。

 

1.2組織腳本

因爲每一個<script>標籤初始下載時,都會阻塞頁面渲染,因此減小頁面包含的<script>標籤數量有助於改善這一狀況。這不只僅針對外鏈腳本,內嵌腳本的數量一樣也要限制。瀏覽器在解析HTML頁面的過程當中每遇到一個<script>標籤,都會因執行腳本而致使必定的延時,所以最小延遲時間將會明顯改善頁面的整體性能。

 

通常狀況下,組織腳本不僅僅是將JS文件中的註釋或者其餘可有可無的內容去掉,並且也要將其壓縮,經過YUI或者是將多個JS文件合併壓縮成一個大的JS文件。只需引用一個<script>標籤,就能夠減小性能的損耗(主要是減小了因加載多個腳本致使的延時)。多個合併壓縮成一個大的JS文件,並將其放在CDN中並引入也是能夠的。

 

1.3無阻塞腳本

JS傾向於阻止瀏覽器的某些處理過程,如HTTP請求和用戶界面更新,這是開發者所面臨的最顯著的性能問題。減小JS文件大小並限制HTTP請求數僅僅是建立響應迅速的Web應用的第一步。Web應用的功能越豐富,所須要的JS代碼就越多,因此精簡源代碼不老是可行的。儘管下載單個較大的JS文件只產生一個HTTP請求,卻會鎖死瀏覽器一大段時間。爲了不這種狀況,你須要向頁面中逐步加載JS文件,這樣作在某種程度上來講不會阻塞瀏覽器。

無阻塞腳本的祕訣在於,在頁面加載完成後才加載JS代碼。用專業術語來講,這意味着在window對象中的load事件觸發後再下載腳本。有多種方式能夠實現這一效果。

 

1.3.1延遲腳本

HTML4爲<script>標籤訂義了一個擴展屬性:defer。Defer屬性指明本元素所含的腳本不會修改DOM,所以代碼能安全地延遲執行。該屬性只有IE4和FireFox3.5+的瀏覽器支持,因此它不是一個理性的跨瀏覽器解決方案。在其餘瀏覽器中,defer屬性會被直接忽略,所以<script>標籤會以默認的方式處理(即會形成阻塞)。然而,若是你的目標瀏覽器支持的話,這仍然是個有用的解決方案。

帶有defer屬性的<script>標籤能夠放置在文檔的任何位置。對應JS文件將頁面解析到<script>標籤時開始下載,但並不會執行,直到DOM加載完成(onload事件被觸發前)。當一個帶有defer屬性的JS文件下載時,它不會阻塞瀏覽器的其餘進程,所以這類文件能夠與頁面中的其餘資源並行下載。

任何帶有defer屬性的<script>元素在DOM完成加載以前都不會被執行,不管內嵌或外鏈腳本都是如此。

例如:

 

<html>
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>Script 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事件處理器執行以前被調用。

固然了,目前我在我本身電腦上執行了上述代碼,基本都不支持defer,可能須要更低的版本才能支持。

 

1.3.2動態腳本元素

經過文檔對象模型,你幾乎能夠用JS動態建立HTML中的全部內容。其根本在於,<script>標籤與頁面中的其餘元素並沒有差別:都能經過DOM引用,都能在文檔中移動、刪除、甚至被建立。用標準的DOM方法能夠很是容易地建立一個新的<script>元素。

 

13.3XMLHttpRequest腳本注入

另一種無阻塞加載的腳本方法是使用XMLHttpRequest對象獲取腳本並注入頁面中。此技術會先建立一個XHR對象,而後用它下載JS文件,最後經過建立動態<script>元素將代碼注入頁面中。

var xmlhttp;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
    document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
    }
  }
xmlhttp.open("GET","test.js",true);
xmlhttp.send();
}

 

這段代碼發送一個GET請求獲取test.js文件。事件處理函數onReadyStateChange檢查readyState是否爲4,同時校驗HTTP狀態碼是否有效(200表示有效響應,304意味着從緩存中讀取)。

這種方法主要優勢是:你能夠下載JS代碼但不當即執行。因爲代碼是在<script>標籤以外返回的,所以它下載後不會自動執行,這使得你能夠把腳本的執行推行到你準備好的時候。另外一個優勢是,一樣的代碼再全部的主流瀏覽器中無一例外都能正常工做。

這種方法的主要侷限性是JS文件必須與所請求的頁面處於相同的域,這意味着JS文件不能從CDN下載。所以大型Web應用一般不會採用XHR腳本注入。

 

1.3.4推薦的無阻塞模式

向頁面中添加大量JS的推薦作法只需兩步:先添加動態加載所需的代碼,而後加載初始化頁面所需的剩下的代碼。由於第一部分的代碼儘可能精簡,甚至可能只包含loadScript()函數,它下載執行都很快,因此不會對頁面有太多影響。一旦初始代碼就位,就用它來加載剩餘的JS。

例如:

<script src="loader.js"></script>
<script>
loadScript("the-rest.js",function(){
    Application.init();
});

 

把這段代碼加載放在</body>閉合標籤以前。這樣作有幾個好處:

(1)確保JS執行過程當中不會阻礙其餘內容顯示;

(2)當第二個JS文件完成下載時,應用所需的全部DOM結構已經建立完畢,並作好交互準備,從而避免了須要另外一個事件(好比window.onload)來檢測頁面是否準備好。

 

小結:

管理瀏覽器中的JS代碼是個棘手的問題,由於代碼執行過程當中會阻塞瀏覽器的其餘進程,好比用戶界面繪製。每次遇到<script>標籤,頁面都必須停下了等待代碼下載(若是是外鏈文件)並執行,而後繼續處理其餘部分。儘管如此,仍是有幾種方法能減小JS對性能的影響:

(1)</body>閉合標籤以前,將全部的<script>標籤放到頁面底部。這能確保在腳本執行前,頁面已經完成渲染;

(2)合併腳本。頁面中的<script>標籤越少,加載也就越快,響應也就越迅速。不管是外鏈仍是內嵌腳本都是如此;

(3)有多種無阻塞下載JS的方法:

  a.使用<script>的defer屬性(注意:高版本瀏覽器不支持);

  b.動態建立<script>元素來下載並執行代碼;

  c.使用XHR對象下載JS代碼並注入頁面中;

經過以上策略,能夠極大的提升那些須要使用大量JS的Web應用的實際性能。

 

個人感觸:

全文本質其實這麼幾個?

1.JS腳本放置最底下(避免延遲致使渲染效果差);

2.合併代碼,將大量JS合併和壓縮爲一個JS文件,本質上減小HTTP請求,同時也減小並行下載帶來的延遲;

作到上述兩點Web應用的性能也會獲得很大程度上的提高,特別是作到2,2也正說明了webpack或者gulp流行的重要緣由。

相關文章
相關標籤/搜索