JavaScript会阻塞浏览器的某些处理过程,如HTTP请求和界面刷新,这是开发者面临的最重要的性能问题。保持JavaScript文件短小,并限制HTTP请求的数量,只是创建反应迅速的网页应用的第一步。一个应用程序所包含的功能越多,所需要的JavaScript代码就越大,保持源代码短小并不总是最佳选择。尽管下载一个大JavaScript文件只产生一次HTTP请求,还是会锁定浏览器一大段时间。为了避开这种情况,需要逐步添加JavaScript。
(1)非阻塞脚本技巧
等页面完成加载之后,再加载JavaScript源代码。这意味着在window的load事件发出之后开始下载JavaScript源代码。
HTML 4为<script>标签定义了一个扩展属性:defer。这个defer属性指明元素中所包含的脚本暂时不修改DOM,因此代码可以稍后执行。defer属性只被IE 4和Firefox 3.5及其更高版本的浏览器所支持,它不是一个理想的跨浏览器解决方案。在其他浏览器上,defer属性被忽略,<script>标签按照默认方式处理,这样又会造成阻塞。如果浏览器支持defer属性,那么这种方法仍是一种有用的解决方案。
<script type=/"text/javascript/"src=/"file1.js/"defer></script>
一个带有defer属性的<script>标签可以放置在文档的任何位置,对应的JavaScript文件将在<script>被解析时启动下载,但直到DOM加载完成,也就是在load事件被调用之前代码不会被执行。
当一个defer的JavaScript文件被下载时,由于它不会阻塞浏览器的其他处理过程,所以这些文件可以与页面的其他资源一起并行下载。
任何带有defer属性的<script>元素在DOM加载完成之前不会被执行,不论是内联脚本还是外部脚本文件。例如,下面代码展示了defer属性如何影响脚本行为。
<html>
<head>
<title></title>
</head>
<body>
<script defer>alert(/"defer/");</script>
<script>alert(/"script/");</script>
<script>
window.onload=function{
alert(/"load/");
};
</script>
</body>
</html>
这些代码在页面处理过程中弹出3个对话框。如果浏览器不支持defer属性,那么弹出对话框的顺序是defer、script和load。如果浏览器支持defer属性,那么弹出对话框的顺序是script、defer和load。
注意:标记为defer的<script>元素不是跟在第二个<script>后面运行,而是在load事件处理之前被调用。
(2)动态脚本加载
如果用户浏览器只包括IE和Firefox,那么defer脚本确实有用。如果需要支持跨领域的多种浏览器,那么还有与defer更一致的实现方式:动态脚本加载。动态脚本加载是非阻塞JavaScript下载中最常用的模式,因为它可以跨浏览器,而且简单易用。
文档对象模型(DOM)允许使用JavaScript动态创建HTML文档内容。<script>元素与页面其他元素没有什么不同:引用变量可以通过DOM进行检索,可以从文档中移动、删除,也可以被创建。一个新的<script>元素可以非常容易地通过标准DOM函数创建。
var script=document.createElement(/"script/");
script.type=/"text/javascript/";
script.src=/"file1.js/";
document.getElementsByTagName_r(/"head/")[0].appendChild(script);
以上代码通过新的<script>元素加载file1.js源文件。此文件在元素被添加到页面之后立刻开始下载。这样无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程,甚至可以将这些代码放在<head>部分也不会对其余部分的页面代码造成影响,下载文件的HTTP连接的情况除外。
当文件使用动态脚本节点下载时,返回的代码通常立即执行(但Firefox和Opera将等待此前的所有动态脚本节点执行完毕)。当脚本是自运行类型时这一机制运行正常,如果脚本只包含供页面其他脚本调用的接口,则会带来问题,在这种情况下,需要跟踪脚本下载完成并准备妥善的情况。可以使用动态<script>节点发出事件得到相关信息。
Firefox、Opera、Chrome和Safari都会在<script>节点接收完成之后发出一个load事件,这样可以监听<script>标签的load事件,以获取脚本准备好的通知。
var script=document.createElement(/"script/")
script.type=/"text/javascript/";
//Firefox、Opera、Chrome、Safari 3+
script.onload=function{
alert(/"Script loaded!/");
};
script.src=/"file1.js/";
document.getElementsByTagName_r(/"head/")[0].appendChild(script);
IE不支持标签的load事件,却支持另一种实现方式,它会发出一个readystatechange事件。<script>元素有一个readyState属性,它的值随着下载外部文件的过程而改变。readyState有5种取值:
❑uninitialized,默认状态。
❑loading,下载开始。
❑loaded,下载完成。
❑interactive,下载完成但尚不可用。
❑complete,所有数据已经准备好。
在<script>元素的生命周期中,readyState的这些取值不一定全部出现,也并没有指出哪些取值总会被用到。不过在实践中loaded和complete状态值很重要。在IE中这两个readyState值所表示的最终状态并不一致,有时<script>元素会得到loader,却从不出现complete,而在另外一些情况下出现complete而用不到loaded。最安全的办法就是在readystatechange事件中检查这两种状态,并且当其中一种状态出现时,删除readystatechange事件句柄,保证事件不会被处理两次。
var script=document.createElement(/"script/")
script.type=/"text/javascript/";
script.onreadystatechange=function{//IE
if(script.readyState==/"loaded/"||script.readyState==/"complete/"){
script.onreadystatechange=null;
alert(/"Script loaded./");
}
};
script.src=/"file1.js/";
document.getElementsByTagName_r(/"head/")[0].appendChild(script);
下面的函数封装了标准实现和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_r(/"head/")[0].appendChild(script);
}
上面的封装函数接收两个参数:JavaScript文件的URL和当JavaScript接收完成时触发的回调函数。属性检查用于决定监视哪种事件。最后设置src属性,并将<script>元素添加至页面。此loadScript函数的使用方法如下:
loadScript(/"file1.js/",function{
alert(/"File is loaded!/");
});
可以在页面中动态加载很多JavaScript文件,只是要注意,浏览器不保证文件加载的顺序。在所有主流浏览器之中,只有Firefox和Opera保证脚本按照指定的顺序执行,其他浏览器将按照服务器返回次序下载并运行不同的代码文件。可以将下载操作串联在一起以保证它们的次序:
loadScript(/"file1.js/",function{
loadScript(/"file2.js/",function{
loadScript(/"file3.js/",function{
alert(/"All files are loaded!/");
});
});
});
此代码待file1.js可用之后才开始加载file2.js,待file2.js可用之后才开始加载file3.js。虽然此方法可行,但是如果要下载和执行的文件很多,还是有些麻烦。如果多个文件的次序十分重要,那么更好的办法是将这些文件按照正确的次序连接成一个文件。独立文件可以一次性下载所有代码,由于这是异步执行,因此使用一个大文件并没有什么损失。