原文作者:Nicholas C. Zakas
原文鏈接:
Loading JavaScript without blocking
譯文:
我看了Steve Souder的博文
scripts without blocking 在這篇博文中他提到動態(tài)的創(chuàng)建一個script元素同時為他分配一個src的屬性會促成下載這個js文件而不阻塞其他的下載或者頁面的其他進程。他的博文 缺少一個如何實現(xiàn)這種方式的例子。所以我想我。我想大多數(shù)開發(fā)者試圖使用javascript腳本庫來實現(xiàn)這種需求(我首先想到了YUI的Get utility)但是很有必要討論一下這個技術的本質。
無阻塞的加載腳本的基本實現(xiàn)方式非常直接。
var scripts=document.createElement('script');
script.type="text/javascript";
script.src="file.js";
document.body.appendChild(scripts);
看 起來就是這么的簡單,你創(chuàng)建了一個新的dom元素,給他它分配相應的屬性同時把它添加到頁面中。這段代碼有兩個方面需要注意。javascript文件直 到script節(jié)點添加到document中才開始下載。這一點和動態(tài)的創(chuàng)建一個img元素,為這個元素分配一個src屬性這時候加載已經(jīng)開始即使該節(jié)點 沒有添加到document中。第二點需要注意的是你既能在<head>中添加該script節(jié)點又能在<body>中添加。這 個無關緊要。這一切的結果就是動態(tài)的加載一個javascript文件而不阻塞頁面。
當然,你可能同時也想知道什么時候 javascript文件完全被加載好和執(zhí)行,同時這也是事情復雜之所在。大多數(shù)modern的瀏覽器(firefox ,safari,opera,chrome)對于scrpt元素有一個load的事件句柄。這是一個確定腳本是否加載好的簡單方式。
//Firefox, Safari, Chrome, and Opera
var scripts=document.createElement('script');
script.type="text/javascript";
script.src="file.js";
scripts.onload=function(){
alert('Script is loaded!')
}
document.body.appendChild(scripts);
真 正的問題出現(xiàn)在ie中,ie使用readyState屬性表明script所處于的狀態(tài)readystatechange事件句柄表明屬性改變的時間。在 這里readyState不和xmlHttpRequest對象中一樣,這里一系列的狀態(tài)值不是一個數(shù)字。在這里,readyState是五個可能是屬性 值。
*“uninitialized” 原始的狀態(tài)。
*"loading"下載開始
*"loaded"下載完成
*"interactive"數(shù)據(jù)處在一種不完全可用
*"complete"所有的數(shù)據(jù)都可以被使用
即 使MSDN documentation表示這些是readyState可用的值,實際上,你不可能看到所有的狀態(tài)。該文檔同時說明其他的元素也支持 readyState同時留給我們一個相當晦澀對于readyState期望值的描述:一個對象的state首先被初始化為unintialized,然 后是loading,當數(shù)據(jù)加載完成,狀態(tài)變?yōu)閘oaded然后是interactive最后是complete狀態(tài)。
對象所有經(jīng)歷的狀態(tài)取決于這個對象。一個對象可以跳過某個狀態(tài)(比如說,interactive)如果這個狀態(tài)沒有被應到到這個對象。
更 奇怪的是最后的狀態(tài)不總是complete。有些時候,readState停止在loaded狀態(tài)而不過渡到complete狀態(tài)。同時有些時候會跳過 loaded狀態(tài)。最好的解決方式是檢查兩個readState值同時在兩個事件中移除事件處理這樣可以確保你不會處理兩次下載。
var scripts=document.createElement('script');
script.type="text/javascript";
script.src="file.js";
document.body.appendChild(scripts);
scripts.onreadystatechange=function(){
if(readyState=="loaded"||readyState=="complete"){
scripts.onreadystatechange=null;
alert('script is loaded!')
}
}
document.body.appendChild(scripts);
你能完美的封裝這兩個解決方式從而創(chuàng)造一個跨瀏覽器的函數(shù)動態(tài)加載腳本。
function loadScript(url,callback){
var script=document.createElement('script');
script.type="text/javascript";
if(script.readyState){
script.onreadystatechange=function(){
if(readyState=="loaded" || readyState=="complete"){
script.onreadystatechange=null;
callback();
}
}
}else{
script.onload=function(){
callback();
}
}
script.src=url;
document.body.appendChild(script);
使用這個函數(shù)的時候,只是傳入一個腳本所在的地址然后在加載好之后調用一個回調函數(shù)。
loadScript("http://yui.yahooapis.com/2.7.0/build/yahoo/yahoo-min.js",
function(){
YAHOO.namespace("mystuff");
//more...
});
以這種方式加載腳本避免腳本阻塞頁面中其他資源的下載或者阻止渲染。對于提高性能這是一種相當有用的技術。真正cool的事情是YUI3構建了一個完美的無阻塞下載方式。所有你需要做的就是下載一個大約20kb的seed文件同時說明你需要下載的其他資源文件。比如這樣
YUI().use("dom", function(Y){
Y.DOM.addClass(document.body, "active");
});
實際上,YUI構建了一個合適的url以便下載dom模塊,當代碼可執(zhí)行時自動執(zhí)行回調函數(shù)。這樣通過異步下載其余的腳本文件從而優(yōu)化了整個頁面下載的時間。
無阻塞的下載一個腳本是一個需要明白的重要技術同時在關注頁面下載性能的web應用程序中應用。javascript 阻塞是整個用戶體驗降低,但是我們已經(jīng)可以對阻塞說不了。