關於js的執行與載入

來源:互聯網
上載者:User

標籤:廣告   時間   動態建立   非同步   多次   文檔   開始   bsp   css   

js在瀏覽器中效能,可以認為是開發人員所面臨的最嚴重的可用性問題了,這個問題因為js的阻塞特性變得很複雜,也就是說瀏覽器在執行js代碼時,不能同時做其他任何事情。事實上,多數瀏覽器使用單一進程來處理使用者介面重新整理和js指令碼的執行,所以只能同一時刻做一件事,js的執行過程耗時越久,瀏覽器等待響應的時間就越長。

簡單的說,這意味著<script>標籤每次出現都霸道地讓頁面等待指令碼的解析和執行。無論當前的js代碼時內嵌還是外連結,頁面的下載和渲染都必須停下來等指令碼的執行完成。這是頁面生存周期中的必要環節,因為指令碼執行過程中可能會修改頁面內容。一個典型例子就死document.write().我們看到的廣告就是這麼搞的。

 

指令碼的位置

  html4規範指出<script>標籤可以放在html文檔的<head>或<body>中,並允許出現多次。按照慣例,<script>標籤用來載入出現在css載入的<link>標籤後。理論上來說,把樣式和行為有關的指令碼放在一起,並先載入它們,這樣做有助於頁面的渲染和互動的正確性。

但是,這樣存在十分嚴重的效能問題,在<head>標籤中載入js檔案,由於指令碼會阻塞頁面的渲染,直到它們全部下載並執行完成後,頁面的渲染的才會執行。要知道,瀏覽器在解析到<body>標籤之前,不會渲染頁面的任何內容,把指令碼放在頁面頂部會導致明顯的延遲,會有明顯的白屏時間,使用者無法瀏覽內容,也無法與頁面進行互動。瀑布圖可以幫我們更清楚地理解效能發生的原因。因此js要放在<body>標籤的底部。

 

 

組織指令碼

    每一個<script>標籤初始下載時都會阻塞頁面渲染,所以減少頁麵包含的<script>標籤數量有助於改善這一情況,這不僅僅是針對外鏈指令碼,內鏈指令碼的數量也要限制,這個問題在處理外鏈指令檔時略有不同,因為http請求還會帶來額外的效能開銷,因此下載單個100kb的檔案將比下載四個25kb的檔案更快,也就是說,減少頁面中指令檔數量將會改善效能。

通常一個大型網站或網路應用需要依賴數個js檔案,我們可以把多個檔案合并成一個,這樣就只需引用一個<script>標籤了。檔案合并可以利用現在的很多構建工具,grunt,gulp等,都很方便。

無阻塞的指令碼

     js傾向於阻止瀏覽器的某些處理過程,如http請求和使用者介面更新,這是開發人員所面臨的最顯著的效能問題。減少js檔案大小並限制http請求僅僅是建立響應迅速的Web應用的第一步,web應用的功能越來越強大豐富,所需要的指令碼代碼也就越多,所以精簡代碼並不總是可行,儘管下載單個較大的js檔案只產生一次http請求,卻會鎖死瀏覽器一大段時間,這樣顯然不是良好的使用者體驗,為避免這種情況,我們需要的是向頁面中逐步載入js檔案,這樣做從某種程度上不會阻塞瀏覽器。

無阻塞指令碼的秘訣在於,在頁面載入完後才載入js代碼,用專業術語說,這意味著window對象的load事件觸發後再下載指令碼,有很多方式可以實現這一效果。

 

《1》延遲的指令碼

      html4為<script>標籤定義了一個擴充屬性,defer。defer屬性指明本元素所含的指令碼不會修改dom,因此代碼可以安全的順延強制。這個屬性目前已經被所有的主流瀏覽器支援了。另外說說HTML5 中引入的async屬性,用於非同步載入指令碼。async和defer的相同點是採用並行下載,在下載的過程不會產生阻塞,區別在於執行的時機,async是載入完成後自動執行,而defer需要等待頁面完成後才執行。

帶有defer屬性的<script>標籤可以放置在文檔的任何位置,對應的js檔案將在解析到<script>標籤時開始下載,但不會執行,直到dom載入完成後(onload事件被觸發前)因此這類檔案可以與頁面中的其他資源並行下載。

<script type=‘type/javascript ‘ src="xiaoai.js" defer></script>

樣本:

<html>

<head>

<title> script defer</title>

</head>

<body>

<script defer>

alert(1);

</script>

<script>

alert(2);

</script>

<script>

window.onload=function(){

alert(3);

}

</script>

</body>

</html>

這段代碼彈出三次提示框,若你的瀏覽器支援defer,彈出的順序為2,1,3;而不支援defer的的瀏覽器則是1,2,3。請注意,帶有defer屬性的瀏覽器不是跟在第二個執行,而是在onload事件之前執行。

 

《2》動態指令碼

   由於DOM的存在,你可以用js建立HTML中幾乎所有內容。其原因在於,<script>元素與頁面其他元素並無差異:都能通過DOM進行引用,都能在文檔中移動,刪除或是被建立。用標準的DOM方法可以很容易的建立一個新的<script>元素:

var script=document.createElement(‘script’);

script.type="text/javascript";

script.src="file1.js";

document.getElementsByTagName(‘head‘)[0].appendChild(script);

   這個新建立的<script>元素載入了file1.js檔案。檔案在該元素被添加到頁面時開始下載。這種技術的重點在於:無論在何時啟動下載,檔案的下載和執行過程不會阻塞頁面的其他進程。你甚至可以將代碼放到頁面<head>地區而不會影響頁面其他部分(用於下載檔案的http連結本身的影響除外)。

 另外,要注意,把新建立的<script>標籤添加到<head>標籤裡比添加到<body>裡更保險,尤其是在頁面載入過程中執行代碼時更是如此。當<body>中的內容沒有載入完成時,IE會拋出“操作已終止”的錯誤資訊。

使用動態指令碼節點下載檔案時,返回的代碼通常會立即執行(除了Firefox和opera,它們會等待此前所有動態指令碼節點執行完畢)。當指令碼‘自執行’時,這種機制運行正常。但是當代碼只包含供頁面其他指令碼調用的介面時,就會有問題。在這種情況下,你必須跟蹤並確保指令碼下載完成且準備就緒。這可以用動態<script>節點觸發的事件來實現。

Firefox,opera,Chrome和Safari以上的版本會在<script>元素接收完成時觸發一個load事件。因此可以通過偵聽此事件來獲得指令碼載入完成時的狀態;

var script=document.createElement(‘script‘)

script.type=‘text/javascript‘;

script.onload=function(){

alert("script loaded");

};

script.src=‘file2.js‘;

document.getElementByTagName(‘head‘)[0].appendChild(script);

IE支援另一種實現方式,它會觸發一個readyStatechange事件。<script>元素提供一個readyState屬性,它的值在外鏈檔案的下載過程的不同階段會發生變化,該屬性有五種取值:

“uninitialized”   初始狀態

“loading” 開始下載

“loaded” 下載完成

“interactive” 資料完成下載但尚不可用

“complete” 所有資料已準備就緒

微軟的相關文檔表明,<script>元素生命週期中,並非readyState的每個取值都會被用到,實際應用中,最有用的兩個狀態就是“loaded”和“complete”。Ie在標識最終狀態時的值並不一致,有時<script>元素達到“loaded”狀態而從不會到達“complete”,有時候直接跳到“complete”而不經過“loaded”,使用這個屬性時最靠譜的方式是同時檢查這兩個狀態,只要其中任何一個觸發,就刪除事件處理器(以確保不會處理兩次)。

 var script=document.createElement(‘script‘)

script.type="text/javascript";

script.onreadystatechange=function(){

if(script.readyState=="loaded"||script.readyState=="complete"){

script.onreadystatechange=null;

alert(‘script loaded‘);

};

script.src=‘file3.js‘;

document.getElementsByTagName(‘head‘)[0].appendChild(script);

}

以上是針對IE的動態載入js檔案方法。

我們需要一個相容各瀏覽器的動態載入js檔案的方法,下面是一個函數封裝了標準和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{//其他瀏覽器

script.onload=function()

{

callback();

};

}

script.src=url;

document.getElementsByTagName(‘head‘)[0].appendChild(script);

}

這個函數接收兩個參數:JavaScript檔案的URL和完成載入後的回呼函數。函數中使用了特徵檢測來決定指令碼處理過程中監聽哪個事件。最後一步是給src屬性賦值,然後將<script>元素添加到頁面。loadscript()函數用法如下

loadscript("file1.js",function(){

alert(‘file is loaded’);

});

如果需要的話,你可以動態載入盡肯能多的jswenjian 到頁面上,但一定要考慮清楚檔案的載入順序。在所有的主流瀏覽器中,只有Firefox和opera能保證指令碼會按照你指定的順序執行,其他瀏覽器會按照從服務端返回的順序下載和執行代碼。因此可以通過下面的串聯方式以確保下載順序。

loadscript(‘file1.js’,function(){

loadscript(‘file2.js‘,function(){

loadscript("file3.js",function(){

alert(‘all file is loaded‘);

});

});

});

下載順序為 file1,file2,file3。

如果多個檔案的下載順序很重要,更好的做法是把他們按正確的順序合并成一個檔案。下載這個檔案就會獲得所有的代碼(由於這個過程是非同步,因此檔案大點沒關係)

總而言之,動態指令碼載入憑藉著它在跨瀏覽器安全色性和易用的優勢,成為最通用的無阻塞載入js的解決方案。

《3》XMLhttpRequest 指令碼注入

另一種無阻塞載入指令碼的方法是使用XMLHttpRequest(XHR)對象擷取指令碼並注入頁面中。

此技術胡建立一個XHR對象,然後用它下載JavaScript檔案,最後通過建立動態<script>元素將代碼注入到頁面中。

var xhr =new XMLHttpRequest();

xhr.open(‘get‘,‘file1.js‘,true);

xhr.onreadystatechange=funcition(){

if(xhr.readyState==4){

if(xhr.status>=200&&xhr.status<300||xhr.status==304){

var script=document.creat.createElement(‘script‘);

script.type="text/javascript";

script.text=xhr.responseText;

document.body.appendChild(script);

}

}

};

chr.send(null);

這段代碼發送一個GET請求擷取file.js檔案。事件處理函數onreadychange檢查readyState是否為4,同時檢驗http狀態代碼是否有效(2xx代表有效響應,304代表從緩衝中讀取)。如果收到了有效響應,就會建立一個<script>元素,設定該元素的text屬性為從伺服器接收到的resposeText。這樣實際上是建立一個帶有內聯指令碼的<script>標籤。一旦新建立的<script>元素被添加到頁面,代碼就會立刻執行然後準備就緒。

這種方法的優點是:你可以下載JavaScript代碼但不立即執行。由於代碼是在<script>標籤之外返回的,因此它下載後不會自動執行,這使得你可以把指令碼執行延遲到你準備好的時候。另一個優點是,同樣的代碼在所有瀏覽器都能正常工作。

這種方法的局限性是:js檔案必須與所請求的頁面處於相同的域,這意味著js檔案不能從cdn下載。因此,大型的web應用通常不會採用XHR指令碼注入技術。

 

小結:

   管理瀏覽器中js代碼是個棘手的問題,因為代碼執行過程會阻塞瀏覽器的其他進程,比如使用者介面繪製。每次遇到<script>標籤,頁面都必須停下來等待所有的js代碼下載並執行,然後恢複處理。儘管如此,還是有幾種方法能減少js對效能的影響:

1.<body>閉合標籤之前,將所有<script>標籤放在頁面底部。這能確保指令碼執行前頁面已經完成渲染。

2.合并指令碼。頁面中<script>標籤越少,載入也就越快,響應也更迅速。無論外鏈檔案還是內嵌的指令碼都是如此。

3.有多種無阻塞下載js的方法:

---<script defer>

---使用動態建立的<script>元素來下載並執行代碼

---使用XHR對象下載js代碼並注入頁面中。

 

關於js的執行與載入

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.