首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议174:使脚本延迟执行

关灯直达底部

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。虽然此方法可行,但是如果要下载和执行的文件很多,还是有些麻烦。如果多个文件的次序十分重要,那么更好的办法是将这些文件按照正确的次序连接成一个文件。独立文件可以一次性下载所有代码,由于这是异步执行,因此使用一个大文件并没有什么损失。