JS的阻塞特性

JS具有阻塞特性,当浏览器在执行js代码时,不能同时做其它事情,即<script>每次出现都会让页面等待脚本的解析和执行(不论JS是内嵌的还是外链的),JS代码执行完成后,才继续渲染页面。

由于,JS的这种阻塞特性,每次遇到<script>,页面都必须停下来等待脚本下载并执行,这会停止页面绘制,带来不好的用户体验。所以,有必要减少JS阻塞特性造成的困扰。

1 优化脚本位置

HTML4规范中,<script>可以放在<head>或<body>中。你可能习惯性的在<head>中放置多个外链JS、CSS,以求优先加载它们。浏览器在继续到<body>之前,不会渲染页面,所以,把JS放在<head>中,会导致延迟。为了提高用户体验,新一代浏览器都支持并行下载JS,但是JS下载仍然会阻塞其它资源的下载(eg.图片)。尽管脚本的下载过程并不会相互影响,但页面仍然必须等待所有JS下载并执行完成才能继续。显见,所有<script>应该尽可能放到<body>的底部,以减少对页面下载的影响。

注意:CSS文件本身是并行下载,不会阻塞页面的其他进程。但是,如果把一段内嵌脚本放在引用外链CSS的<link>之后会导致页面阻塞去等待CSS的下载。这样做是为了确保内嵌脚本在执行时能够获得正确的样式信息。所以,最好不要把内嵌脚本放在CSS的<link>之后。

2 减少外链脚本数量以改善性能

 原因很简单,额外的HTTP请求会带来额外的开销,所以减少页面中外链脚本的数量,有助于改善性能。

3 使用无阻塞下载JS方法

无阻塞脚本的秘诀在于,在页面加载完成后才加载JS,即在window对象的load事件触发后在下载脚本。

3.1 使用<script>的defer属性(仅IE和Firefox3.5以上);

defer属性指明本元素所含的脚本不会修改DOM,因此代码能安全的延迟执行。defer属性的<script>,对应的JS文件将在页面解析到<script>时开始下载,但并不会执行,直到DOM加载完成,即onload事件触发前被调用。当一个带有defer属性的JS文件下载时,他不会阻塞浏览器的其它进程,因此这类文件可以与页面中的其他资源并行下载。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DeferredScripts</title>
</head>
<body>

<script type="text/javascript" defer>
alert("defer");
</script>

<script type="text/javascript">
alert("script");
</script>

<script type="text/javascript">
window.onload = function() {
alert("load");
};
</script>

</body>
</html>

 

对于支持defer的浏览器弹出顺序是:script>defer>load;而不支持该属性的浏览器的弹出顺序为:defer>script>load。

3.2 使用动态创建的<script>元素来下载并执行代码

实例代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DynamicScriptElements</title>
</head>
<body>

<script type="text/javascript">
function loadScript(url, callback) {
var script = document.createElement("Script");
script.type = "text/javascript";

//IE 验证脚本是否下载完成
if (script.readyState) {
script.onreadystatechange = function() {
//readyState属性有5种取值
//uninitialized:初始状态
//loading:开始下载
//interactive:数据完成下载但尚不可用
//complete:数据已经准备就绪
//实际使用时,readyState的值并不像我们预想的那样有规律,实践发现使用readyState
//最靠谱的方式是同时检查以下2个状态,只要其中1个触发,就认为脚本下载完成。
if (script.readyState == "loaded" || script.readyState == "complete") {
//移除事件处理器,确保事件不会处理2次
script.onreadystatechange = null;
callback();
}
};
}

//其他浏览器
else {
script.onload = function() {
callback();
};
}

script.src = url;
//把新建的<Script>添加到<head>里比添加到<body>里更保险。
document.getElementsByTagName("head")[0].appendChild(script);
}

//动态加载多个JS文件
//优先加载Common.js,等待Common.js加载完毕后加载Costom.js
//不同浏览器的执行顺序不同
//Firefox、Opera能够保证按照你脚本的加载顺序来执行
//其他浏览器会按照从服务端返回的顺序执行代码,因此使用嵌套的方法保证调用顺序
loadScript("Common.js", function() {
loadScript("Costom.js", function() {
alert("all load");
});
});
</script>

</body>
</html>

文件在该元素被添加到页面时开始下载。这种技术的重点在于:无论在何时启动下载,文件的下载与执行不会阻塞页面的其他进程。使用动态脚本节点下载文件时,根据浏览器不同,多数浏览器,返回的代码会立即执行(Firefox、Opera,会等待此前所有动态节点执行完毕)。当脚本”自执行“时,这种机制运行正常,但是当代码内只包含供其它脚本调用的接口时,就必须确保脚本下载完成并准备就绪,在上例中列举了不同浏览器的验证方法。

注意:如果多个文件的顺序很重要,更好的做法是把它们按正确顺序合并为一个文件。此外,说把新建的<Script>添加到<head>里比添加到<body>里更保险是因为要尽量避免页面报错(在低版本的IE中使用不当会发生"操作已中止"错误

3.3 使用XHR对象下载JS代码并注入页面中

实例代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XhrScriptInjection</title>
</head>
<body>

<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("get", "JScript.js", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
//2XX表示有效响应,304表示从缓存读取
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); //一旦新创建的<script>被添加到页面,代码就立刻执行然后准备就绪。
}
}
};
xhr.send(null);
</script>

</body>
</html>

这种方法的优点是,你可以下载JS代码但不立即执行。由于代码是在<script>标签之外返回的,因此它下载后不会自动执行,这使得你可以把脚本的执行推迟到你准备好的时候。另一个优点是,同样的代码在所有主流浏览器中都能正常工作。

这种方法的主要局限性是JS文件必须与所有请求的页面处于相同的域。
综上所述,向页面中添加大量JS的推荐做法只需两步:先添加动态加载的所需代码,然后加载初始化页面所需的剩下代码。

    <script type="text/javascript" src="Common.js"></script>

<script type="text/javascript">
loadScript("Costom.js", function() {
//Do Something
});
</script>

优化前:

优化后:

 

"操作已中止"错误
<html>
<head>
<title>Operation Aborted Example</title>
</head>
<body>
<p>The following code should cause an Operation Aborted error in IE versions prior to 8.</p>
<div>
<script type="text/javascript">
document.body.appendChild(document.createElement("div"));
</script>
</div>
</body>
</html>

上述代码在低版本IE中会报"操作已中止"错误。出现此问题的原因是子容器 HTML 元素包含试图修改父容器元素的子容器的脚本。脚本试图使用 innerHTML 方法或 appendChild 方法修改父容器元素。例如对于如果 DIV 元素是一个 BODY 元素中的子容器,并且在 DIV 元素中的一个 SCRIPT 块试图修改 DIV 元素的父容器的 BODY 元素可能会出现此问题。

最简单的解决方法:将脚本移到body元素的范围。

<html>
<head>
<title>Operation Aborted Example</title>
</head>
<body>
<p>The following code should cause an Operation Aborted error in IE versions prior to 8.</p>
<div>
</div>
<script type="text/javascript">
document.body.appendChild(document.createElement("div"));
</script>
</body>
</html>

其它解决方法可以参考:

http://www.nczonline.net/blog/2008/03/17/the-dreaded-operation-aborted-error

http://support.microsoft.com/kb/927917/zh-cn

posted on 2011-12-21 15:10  BobbyPeng  阅读(1250)  评论(0编辑  收藏  举报

导航