多数情况下,浏览器是用单一进程处理用户界面和JavaScript脚本执行。所以,JavaScript的处理时间越久,浏览器等待响应事件就越长。
简单地说,就是浏览器每次处理<script>标签,就会让页面等待脚本的解析和执行。这个过程页面渲染和用户交互是完全阻塞的。
一、脚本位置
H4规范指出,<script>标签可以放在<head>和<body>标签内,并允许出现多次。
1.1 将脚本放在底部
正常情况下,多个脚本放到<head>内,浏览器会一个一个脚本加载与执行,如果这个过程耗时过长,通常就会显示空白页面。
IE8,Firefox3.5、Safari4和Chrome2都允许并行下载JavaScript脚本。所以,当加载一个<script>标签时,不会阻塞其他<script>标签,但是会阻塞其他资源的下载,比如图片等。
二、组织脚本
由于<script>标签初始下载都会阻塞页面渲染,所以减少<script>标签的数量会改善阻塞情况。这不仅仅包含外链脚本,也包含内嵌脚本。
2.1 单个100k的文件将比4个25k的文件加载快
考虑到HTTP请求会带来额外的性能开销,单个100k的文件将比4个25k的文件加载快。当依赖多个脚本文件时,尽量合并,用一个<script>标签引入。
2.2 页面引入的脚本尽量是本网站的,而不是其他网站的。
同样考虑HTTP请求的开销,请求本网站的脚本文件会比请求其他网站的脚本文件更快。
三、无阻塞的脚本
减小JavaScript文件大小和控制HTTP请求数
JavaScript倾向于阻塞浏览器的某些处理过程,比如HTTP请求和页面的更新,这是最显著的影响性能的问题。因此,精简代码数量和HTTP请求数很必要。
加载单个较大脚本文件会锁死浏览器一大段时间
尽管下载单个较大脚本文件只产生一次HTTP请求,但是会锁死浏览器一大段时间。因此需要逐步加载JavaScript文件,这样做在某种程度上来说不会阻塞浏览器。
总结的说:就是页面加载完后再加载JavaScript代码。
3.1 延迟的脚本
3.1.1 defer属性
H4给<script>标签定义了一个扩展属性:defer。
带有defer属性的<script>标签可以放到任意一个位置,因为,当解析到带defer的<script>标签时,只会下载脚本,而不会执行脚本,直到DOM加载完成。因为defer不会阻塞浏览器的其他进程,可以和其他资源并行下载。
局限性:只有IE 4+ 和Firefox 3.5+的浏览器支持。
3.2 动态脚本元素
3.2.1 DOM创建<script>标签
<script>标签和其他标签并无差异,可以DOM方法创建、移动、删除<script>元素。
新创建的<script>标签如果下载JavaScript文件,无论何时下载都不会阻塞页面其他进程,放到<head>里都不会影响其他进程。
通常,新创建的<script>标签放到<head>里比放到<body>里面更保险。因为<body>没完全加载完成时,IE浏览器可能抛出‘操作已中止’的异常。
3.2.3 readystatechange事件
IE支持另外一种实现方式,它会触发readystatechange事件。
<script>元素提供一个readyState属性,它的值在外链文件下载过程的不同阶段会发生变化。
该属性有五种取值:
初始状态:uninitialized 开始下载:loading 下载完成:loaded 数据完成下载但不能用:interactive 所以数据已准备就绪:complate
var script = document.createElement("script");
script.type = "text/javascript";
// IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complate") {
script.onreadystatechange = null;
alert("Script loaded.");
}
}
script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);
上述代码这么做是因为在<script>的生命周期内,并非readyState的每个值都会被用到,这些值也不一定会被用到。最有用的两个值是loaded和complate。
最靠谱的就是检测这两个状态,以确保事件不会处理两次。
3.3 XMLHttpRequest脚本注入
先创建XHR对象,再用它下载JavaScript文件,最后通过创建动态<script>元素将代码注入到页面中:
var xhr = new XMLHttpRequest();
xhr.open("get", "file1.js", true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
if (xhr.state >= 200 && xhr.state < 300 || xhr.state == 304){
var script = document.screateElement("script");
script.style = "text/javascript";
script.text = xhr.responseText;
document.body.appendChild(scripy);
}
}
};
xhr.send(null);
这种方法的好处就是下载下来的脚本文件不会立即执行,另一优点就是它在所有主流浏览器中都可以正常工作。
缺点是请求的脚本文件与页面要在同一个域内。因此大型的web应用不会用这种方法。
3.4 推荐的无阻塞模式
3.4.1 先加载页面初始化所必要的脚本代码,等页面初始化完成,再加载剩下的脚本代码。
<script type="text/javascript" src="loader.js"></script>
<script type="text/javascript">
loadScript("the-rest.js", function(){
Application.init();
});
</script>
这样确保js执行过程不会阻塞页面其他内容的显示。第二个<script>执行时,DOM结构已经创建完毕,并做好了交互的准备。从而避免了需要另一个事件(比如windows.load)来检测页面是否准备好。
3.4.2 将loadScript()函数直接嵌入到页面,避免多产生一次HTTP请求。