面對(duì)開發(fā)者的瀏覽器的Js性能可以說,最重要的是可用性問題砍艾。這個(gè)問題是復(fù)雜的蒂教,因?yàn)镴s的阻塞特性,換句話說當(dāng)Js代碼正在被執(zhí)行的時(shí)候脆荷,任何其他事情都不會(huì)發(fā)生凝垛。事實(shí)上懊悯,大多數(shù)瀏覽器使用單個(gè)進(jìn)程來處理UI更新和Js執(zhí)行,所以在任何時(shí)刻只有一個(gè)能發(fā)生梦皮。Js占用約多的時(shí)間來執(zhí)行炭分,瀏覽器能夠響應(yīng)用戶操作之前等待的時(shí)間就越長(zhǎng)。
在基礎(chǔ)層面剑肯,意味著每一個(gè)<script>標(biāo)簽都會(huì)使頁面等待腳本被解析和執(zhí)行捧毛,不論Js代碼是內(nèi)聯(lián)的還是外聯(lián)的。頁面下載和渲染必須停止并等待腳本完成執(zhí)行让网,這個(gè)是頁面聲明周期中必要的部分呀忧,因?yàn)槟_本執(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)簽時(shí)而账,沒有辦法知道Js是否會(huì)在<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>
雖然這段代碼看上去無害的虽界,它實(shí)際上有一個(gè)嚴(yán)重的性能問題:有三個(gè)Js文件在<head>中被加載,因?yàn)槊恳粋€(gè)<script>標(biāo)簽阻塞頁面繼續(xù)渲染涛菠,直到腳本完全地被下載和執(zhí)行莉御。記住,在打開<body>標(biāo)簽之前俗冻,瀏覽器不會(huì)呈現(xiàn)任何頁面內(nèi)容礁叔。把Js放在頁面頭部導(dǎo)致了顯著的延遲,通常用戶還沒有開始閱讀或者與頁面交互之前迄薄,就會(huì)出現(xiàn)空白的頁面琅关。為了懂得如何發(fā)生了,查看下每個(gè)資源下載的瀑布流圖還是很有用的
現(xiàn)在瀏覽器都允許并行下載Js文件了讥蔽,這是一個(gè)好消息涣易,因?yàn)?lt;script>標(biāo)簽不必阻塞其他<script>標(biāo)簽下載外部資源了。不幸的是冶伞,依然阻塞其他資源的下載新症,比如圖片。即使下載Js不阻塞其他資源下載响禽,頁面還是必須等待Js代碼下載和執(zhí)行徒爹。問題還是沒有解決荚醒。
因?yàn)槟_本阻塞下載頁面其他資源,所以推薦把所有的<script>標(biāo)簽盡可能放在<body>標(biāo)簽的底部隆嗅,以不影響整個(gè)頁面的下載腌且,比如:
<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>
合并腳本
因?yàn)槊恳粋€(gè)<script>標(biāo)簽在頁面初始化下載的時(shí)候阻塞頁面渲染,限制頁面上<script>標(biāo)簽的數(shù)量是有幫助的榛瓮。
每一個(gè)Http請(qǐng)求帶來額外的性能開銷铺董,所以下載單個(gè)100KB的文件要比下載4個(gè)25KB的文件來得快。所以禀晓,現(xiàn)在外部Js文件的數(shù)量可以提升頁面的性能精续。
典型的大網(wǎng)站或web應(yīng)用程序會(huì)有多個(gè)Js文件請(qǐng)求。你可以通過合并這些文件為一個(gè)Js文件粹懒,然后使用一個(gè)<script>標(biāo)簽引入重付,來最小化性能影響。
比如凫乖,雅虎創(chuàng)建了用于分發(fā)Yahoo組合的處理程序确垫,如何網(wǎng)站可以通過一個(gè)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>
這個(gè)URL加載了2.7.0版本的yahoo-min.js和event-min.js文件帽芽。這些文件在服務(wù)器上分開存放删掀,但是當(dāng)URL請(qǐng)求的時(shí)候合并在一起返回。使用單個(gè)<script>標(biāo)簽來加載导街,而不是通過兩個(gè)<script>標(biāo)簽披泪。
非阻塞Script
保持Js文件小并且限制http請(qǐng)求數(shù)量只是對(duì)于小型網(wǎng)站的第一步。對(duì)于有這豐富功能的應(yīng)用搬瑰,有更多的Js代碼請(qǐng)求款票,所以保持源代碼小不總是一個(gè)選擇。限制只下載單個(gè)大的Js文件將導(dǎo)致鎖死瀏覽器一個(gè)較長(zhǎng)的時(shí)間泽论,盡管它只有一個(gè)HTTP請(qǐng)求艾少。為了解決這種情況,你需要使用一種非阻塞瀏覽器的方式在頁面中增量的添加更多的Js翼悴。
非阻塞腳本的秘密是在頁面加載完成后加載Js缚够。從技術(shù)層面講,就是在window's load事件觸發(fā)后下載Js代碼抄瓦,有幾個(gè)技術(shù)可以做到潮瓶。
Deferred Script
<script type="text/javascript" src="file1.js" defer></script>
一個(gè)帶有defer的<script>標(biāo)簽可以放置在文檔的任何位置,Js文件會(huì)在<script>標(biāo)簽被解析后開始下載钙姊,但是不會(huì)執(zhí)行毯辅,知道DOM被完全加載(在onload事件之前執(zhí)行)。當(dāng)deferred腳本文件被下載的時(shí)候煞额,它不阻塞瀏覽器思恐,所以頁面上的其他資源能并行下載沾谜。
動(dòng)態(tài)腳本元素
Dom允許你使用js動(dòng)態(tài)創(chuàng)建HTML文檔的幾乎任何部分,當(dāng)然包括<script>標(biāo)簽胀莹。一個(gè)新的<script>標(biāo)簽可以使用標(biāo)準(zhǔn)的DOM方法來簡(jiǎn)單的創(chuàng)建:
var script = document.createElement("script");
script.type="text/javascript";
script.src="file1.js";
document.getElementsByTagName("head")[0].appendChild(script);
這個(gè)新的<script>元素加載源文件file1.js基跑,一旦元素被添加到頁面,文件就開始下載描焰。關(guān)于這個(gè)技術(shù)最重要的是文件下載和執(zhí)行不會(huì)阻塞頁面其他進(jìn)程媳否,不管下載在哪兒初始化。你甚至能夠放置你的代碼在文檔<head>中荆秦,也不會(huì)影響頁面其他部分篱竭。
當(dāng)使用動(dòng)態(tài)腳本節(jié)點(diǎn)加載的文件下載完成,這代碼就會(huì)立刻執(zhí)行步绸,當(dāng)腳本是獨(dú)立有效的掺逼,這會(huì)工作的很好。但可能腳本的執(zhí)行依賴于頁面上其他腳本瓤介,在這種情況下吕喘,你需要跟蹤代碼被完全下載并準(zhǔn)備使用的狀態(tài),這是通過事件觸發(fā)來完成的(腳本加載完成會(huì)觸發(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);
通常氯质,你會(huì)考慮使用簡(jiǎn)單的方法去動(dòng)態(tài)加載Js文件,下面是一個(gè)方法封裝:
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);
}
你可能動(dòng)態(tài)加載很多JS文件漾月,但是你得考慮文件加載的順序病梢。主流的瀏覽器為確保腳本執(zhí)行的順序會(huì)按照你指定的來,其他瀏覽器下載和執(zhí)行腳本的順序由他們被服務(wù)器返回的先后順序決定梁肿。你可以通過鏈?zhǔn)较螺d來控制順序,比如:
loadScript("file1.js",function(){
loadScript("file2.js",function(){
loadScript("file3.js",function(){
alert("All files are loaded!");
})
})
})
XMLHttpRequest 腳本注入
另一個(gè)非阻塞檢索腳本的方法是使用XMLHttpRequest對(duì)象觅彰,然后注入腳本到頁面吩蔑。
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);
這個(gè)方法主要的優(yōu)勢(shì)是你可以下載腳本代碼,但不需要立刻執(zhí)行它填抬。因?yàn)榇a不是通過<script>標(biāo)簽下載的烛芬,它不會(huì)一旦下載就立刻執(zhí)行,允許你延遲它的執(zhí)行飒责,直到你已經(jīng)準(zhǔn)備好讓它執(zhí)行赘娄。