高性能JS-加載和執(zhí)行

面對開發(fā)者的瀏覽器的Js性能可以說,最重要的是可用性問題。這個問題是復(fù)雜的,因為Js的阻塞特性,換句話說當(dāng)Js代碼正在被執(zhí)行的時候,任何其他事情都不會發(fā)生。事實上,大多數(shù)瀏覽器使用單個進程來處理UI更新和Js執(zhí)行,所以在任何時刻只有一個能發(fā)生。Js占用約多的時間來執(zhí)行,瀏覽器能夠響應(yīng)用戶操作之前等待的時間就越長。

在基礎(chǔ)層面,意味著每一個<script>標(biāo)簽都會使頁面等待腳本被解析和執(zhí)行,不論Js代碼是內(nèi)聯(lián)的還是外聯(lián)的。頁面下載和渲染必須停止并等待腳本完成執(zhí)行,這個是頁面聲明周期中必要的部分,因為腳本執(zhí)行過程中引起頁面改變。典型的例子是在頁面中使用document.write(),比如:

<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>

當(dāng)瀏覽器碰到<script>標(biāo)簽時,沒有辦法知道Js是否會在<p>標(biāo)簽內(nèi)插入內(nèi)容,因此瀏覽器停止處理頁面,執(zhí)行Js代碼,然后繼續(xù)解析和渲染頁面。

腳本位置

傳統(tǒng)的,<script>標(biāo)簽放在<head>標(biāo)簽內(nèi)用于加載Js文件,<link>tags加載外部的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>
</head>
<body>
  <p>Hello world!</p>
</body>
</html>  

雖然這段代碼看上去無害的,它實際上有一個嚴(yán)重的性能問題:有三個Js文件在<head>中被加載,因為每一個<script>標(biāo)簽阻塞頁面繼續(xù)渲染,直到腳本完全地被下載和執(zhí)行。記住,在打開<body>標(biāo)簽之前,瀏覽器不會呈現(xiàn)任何頁面內(nèi)容。把Js放在頁面頭部導(dǎo)致了顯著的延遲,通常用戶還沒有開始閱讀或者與頁面交互之前,就會出現(xiàn)空白的頁面。為了懂得如何發(fā)生了,查看下每個資源下載的瀑布流圖還是很有用的


HX[YHN7%(HAQLWP3NQD]ILL.png

現(xiàn)在瀏覽器都允許并行下載Js文件了,這是一個好消息,因為<script>標(biāo)簽不必阻塞其他<script>標(biāo)簽下載外部資源了。不幸的是,依然阻塞其他資源的下載,比如圖片。即使下載Js不阻塞其他資源下載,頁面還是必須等待Js代碼下載和執(zhí)行。問題還是沒有解決。

因為腳本阻塞下載頁面其他資源,所以推薦把所有的<script>標(biāo)簽盡可能放在<body>標(biāo)簽的底部,以不影響整個頁面的下載,比如:

<html>
<head>
  <title>Script Example</title>
  <link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  <p>Hello world!</p>
 
  <-- Example of recommanded 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>標(biāo)簽在頁面初始化下載的時候阻塞頁面渲染,限制頁面上<script>標(biāo)簽的數(shù)量是有幫助的。

每一個Http請求帶來額外的性能開銷,所以下載單個100KB的文件要比下載4個25KB的文件來得快。所以,現(xiàn)在外部Js文件的數(shù)量可以提升頁面的性能。

典型的大網(wǎng)站或web應(yīng)用程序會有多個Js文件請求。你可以通過合并這些文件為一個Js文件,然后使用一個<script>標(biāo)簽引入,來最小化性能影響。

比如,雅虎創(chuàng)建了用于分發(fā)Yahoo組合的處理程序,如何網(wǎng)站可以通過一個combo-handled URL拉取任何數(shù)量的YUI文件。比如:

<html>
<head>
  <title>Script Example</title>
  <link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  <p>Hello world!</p>
  
  <-- Example of recommanded script positioning -->
  <script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js"></script>
</body>
</html>

這個URL加載了2.7.0版本的yahoo-min.js和event-min.js文件。這些文件在服務(wù)器上分開存放,但是當(dāng)URL請求的時候合并在一起返回。使用單個<script>標(biāo)簽來加載,而不是通過兩個<script>標(biāo)簽。

非阻塞Script

保持Js文件小并且限制http請求數(shù)量只是對于小型網(wǎng)站的第一步。對于有這豐富功能的應(yīng)用,有更多的Js代碼請求,所以保持源代碼小不總是一個選擇。限制只下載單個大的Js文件將導(dǎo)致鎖死瀏覽器一個較長的時間,盡管它只有一個HTTP請求。為了解決這種情況,你需要使用一種非阻塞瀏覽器的方式在頁面中增量的添加更多的Js。

非阻塞腳本的秘密是在頁面加載完成后加載Js。從技術(shù)層面講,就是在window's load事件觸發(fā)后下載Js代碼,有幾個技術(shù)可以做到。

Deferred Script

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

一個帶有defer的<script>標(biāo)簽可以放置在文檔的任何位置,Js文件會在<script>標(biāo)簽被解析后開始下載,但是不會執(zhí)行,知道DOM被完全加載(在onload事件之前執(zhí)行)。當(dāng)deferred腳本文件被下載的時候,它不阻塞瀏覽器,所以頁面上的其他資源能并行下載。

動態(tài)腳本元素

Dom允許你使用js動態(tài)創(chuàng)建HTML文檔的幾乎任何部分,當(dāng)然包括<script>標(biāo)簽。一個新的<script>標(biāo)簽可以使用標(biāo)準(zhǔn)的DOM方法來簡單的創(chuàng)建:

var script = document.createElement("script");
script.type="text/javascript";
script.src="file1.js";
document.getElementsByTagName("head")[0].appendChild(script);

這個新的<script>元素加載源文件file1.js,一旦元素被添加到頁面,文件就開始下載。關(guān)于這個技術(shù)最重要的是文件下載和執(zhí)行不會阻塞頁面其他進程,不管下載在哪兒初始化。你甚至能夠放置你的代碼在文檔<head>中,也不會影響頁面其他部分。

當(dāng)使用動態(tài)腳本節(jié)點加載的文件下載完成,這代碼就會立刻執(zhí)行,當(dāng)腳本是獨立有效的,這會工作的很好。但可能腳本的執(zhí)行依賴于頁面上其他腳本,在這種情況下,你需要跟蹤代碼被完全下載并準(zhǔn)備使用的狀態(tài),這是通過事件觸發(fā)來完成的(腳本加載完成會觸發(fā)load事件)。

var script=document.createElement("script");
script.type="text/javascript";

script.onload=function(){
  alert("Script loaded!");
};

script.src="file1.js";
document.getElementsByTagName("head")[0].appendChild(script);

通常,你會考慮使用簡單的方法去動態(tài)加載Js文件,下面是一個方法封裝:

function loadScript(url,callback){
  var script =document.createElement("script");
  script.type="text/javascript";
  
  if(script.readyState){
    script.onreadystatechange=function(){
      if(script.readyState=='loaded' || script.readyState=='complete'){
        script.onreadystatechange=null;
        callback();
      }
    }
  }else{
    script.onload=function(){
      callback();
    };
  }
  script.src=url;
  document.getElementsByTagName("head")[0].appendChild(script);
}

你可能動態(tài)加載很多JS文件,但是你得考慮文件加載的順序。主流的瀏覽器為確保腳本執(zhí)行的順序會按照你指定的來,其他瀏覽器下載和執(zhí)行腳本的順序由他們被服務(wù)器返回的先后順序決定。你可以通過鏈?zhǔn)较螺d來控制順序,比如:

loadScript("file1.js",function(){
  loadScript("file2.js",function(){
    loadScript("file3.js",function(){
      alert("All files are loaded!");
    })
  })
})

XMLHttpRequest 腳本注入

另一個非阻塞檢索腳本的方法是使用XMLHttpRequest對象,然后注入腳本到頁面。

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);

這個方法主要的優(yōu)勢是你可以下載腳本代碼,但不需要立刻執(zhí)行它。因為代碼不是通過<script>標(biāo)簽下載的,它不會一旦下載就立刻執(zhí)行,允許你延遲它的執(zhí)行,直到你已經(jīng)準(zhǔn)備好讓它執(zhí)行。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容