JavaScript的加載和執(zhí)行(Loading and Execution)
JavaScript的阻塞特性
- 當瀏覽器遇到JavaScript代碼段時(不管是內(nèi)嵌還是外鏈),由于不確定其是否會進行DOM操作,所以都會等待腳本的解析和執(zhí)行完成后,再往下運行。此時頁面渲染和用戶交互都是被阻塞的,這就是 JavaScript的阻塞特性 。
JavaScript阻塞特性的思考
- 雅虎性能小組對JS代碼優(yōu)化的首要規(guī)則就是,通過改變
<script>腳本的位置來減少對頁面加載的影響。也就是說將<script>元素放到<body>標簽底部,這樣頁面的CSS和圖片等資源文件加載時,就不需要等待JS代碼的解析和執(zhí)行了。 - 除此之外,減少
<script>標簽的數(shù)量也能改善阻塞的情況- 我們可以合并
<script>代碼段,因為瀏覽器在解析HTML頁面時,每遇到一個<script>標簽,都會因為執(zhí)行腳本而導(dǎo)致一定的延時。 - 我們還需要盡可能的合并外鏈的JavaScript文件。測試發(fā)現(xiàn),下載一個100KB的JS文件比下載四個25KB的JS文件要快,是因為每次HTTP請求相當于重新進行一次Socket連接,會產(chǎn)生性能損耗。
- 我們可以合并
實現(xiàn)Noblocking Scripts(無阻塞的腳本)的幾種方式
-
Deferred Scripts (腳本延遲)
針對JavaScript的阻塞特性,很多人首先想到的辦法可能是:能不能先下載JS文件,等頁面加載完成后,再去解析和執(zhí)行引入的JavaScript代碼呢?
針對此種設(shè)想,HTML4標準為<script>標簽提供了defer屬性。意味著可以先下載JS文件(同時允許并行下載),等到DOM加載完成(即onload事件被觸發(fā)前),才會執(zhí)行其中的代碼段。<script type="text/javascript" src="file1.js" defer/>注意:該屬性只對聲明了
src屬性的<script>標簽生效,另外HTML5規(guī)范中引入了async屬性,和defer屬性一樣,也是用于異步加載腳本的 -
Dynamic Script Elements (動態(tài)腳本元素)
我們知道DOM(文檔對象模型)機制使得我們可以動態(tài)的創(chuàng)建、編輯、移動和刪除HTML元素,這里面當然也包含<script>元素。也就是說,其實我們是可以通過JavaScript代碼來動態(tài)構(gòu)建<script>標簽的。//創(chuàng)建script元素 var script = document.creatElement("script"); //指定元素的type屬性 script.type = "text/javascript"; //指定URL script.src = "file1.js"; //將新的script標簽加入到文檔頭部 document.getElementByTagName("head")[0].appendChild(script);注意,當新的
<script>元素被加入到<head>時,會立即開始下載file1.js文件,并執(zhí)行其中的JS腳本。這種方式的優(yōu)勢就在于,能夠把<script>腳本放在頁面<head>區(qū)域,避免影頁面其他部分的加載。
如果需要監(jiān)聽腳本的下載狀態(tài),可以通過onload()方法監(jiān)聽腳本加載完成的狀態(tài):var script = document.creatElement("script"); script.type = "text/javascript"; //js下載完畢后的監(jiān)聽(IE中不可用) script.onload = function(){ alert("script loaded!"); }; script.src = "file1.js"; document.getElementByTagName("head")[0].appendChild(script);但是onload()方法僅在Firefox、Chrome、Opera和Safari3+ 瀏覽器中支持,如果想在IE瀏覽器要監(jiān)聽js腳本的下載狀態(tài),則可以通過監(jiān)聽
<script>元素的readyState狀態(tài)屬性來實現(xiàn)。
readyState有五種取值,但是微軟的相關(guān)文檔表明,在整個<script>元素生命周期中,并非readyState的每個值都會用到。最終表明<script>元素加載完畢的是"loaded"和"complete"兩個狀態(tài),所以我們要檢查這兩個狀態(tài)。var script = document.creatElement("script"); script.type = "text/javascript"; //js下載完畢后的監(jiān)聽(IE) script.onreadystatechange = function(){ if(script.readyState == "loaded" || script.readyState == "complete"){ alert("script loaded!"); } }由此可見通過
動態(tài)腳本元素的方式解決JavaScript的阻塞問題時,如果需監(jiān)聽下載狀態(tài),還需要針對不同瀏覽器做封裝處理。//定義公共的js加載方法(有沒有覺得這種callback的方式和$.Ajax很類似?) function loadScript(url,callback){ var script = document.creatElement("script"); script.type = "text/javascript"; //判斷如果能獲取readyState屬性,則表明是IE瀏覽器 if(script.readyState){ script.onreadystatechange = function(){ if(script.readyState == "loaded" || script.readyState == "complete"){ script.onreadysatechange = null; callback(); } } //其他瀏覽器 }else{ script.onload = function(){ callback(); } } script.src = url; document.getElementByTagName("head")[0].appendChild(script); } //調(diào)用 loadScript("file1.js",function(){ alert("script loaded!"); });
-
XMLHttpRequest Script Injection (XMLHttpRequest腳本注入)
另一種無阻賽加載腳本的方式,是使用XMLHttpRequest(XHR)對象加載JS腳本
由于XMLHttpRequest腳本注入的方式,由JS腳本的加載以及<script>元素的創(chuàng)建兩部分組成,我們就可更加靈活的控制JavaScript腳本,比如先加載,等到需要時候再執(zhí)行。
//創(chuàng)建XMLHttpRequest對象
var xhr = new XMLHttpRequest();
//通過GET請求加載file1.js
xhr.open("get","file1.js",true);
//通過XHR對象的onreadystatechange()方法監(jiān)聽readState屬性的變化
xhr.onreadystatechange = function(){
//readyState屬性表示XHR對象請求的就緒狀態(tài):0-請求未初始化;1-請求已建立;2-請求已發(fā)送;3-請求處理中;4-響應(yīng)已完成
if(xhr.readyState == 4){
//status屬性表示HTTP請求的狀態(tài)碼:200-(OK)請求成功;304-(Not Modified)從緩存中讀??;狀態(tài)碼小于300都表示請求成功
if(xhr.status >= 200 && (xhr.status < 300 || xhr.status == 304)){
//創(chuàng)建script元素
var script = document.creatElement("script");
//指定元素的type屬性
script.type = "text/javascript";
//將response響應(yīng)內(nèi)容填充到script元素中
script.text = xhr.responseText;
//當把script元素添加到body時,js代碼會被立即執(zhí)行
document.body.appendChild(script);
}
}
}
```