JS 脚本的阻塞特性
约 1250 字大约 4 分钟
2025-03-12
一、什么是阻塞特性?
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>
之后。
// 实际开发过程中,<link> 后边不要紧跟着内联脚本
<link rel="stylesheet" href="styles.css">
<script>
window.addEventListener('load', function() {
// 页面加载完成后执行
console.log('Page fully loaded');
});
</script>
2、合理控制 JS 脚本数量
浏览器遇到 script 标签,会停下来等待下载和执行。
因此减少文件数量有助于减少等待时间,同时注意控制文件体积,过大的文件体积会增加下载时间。
3、利用 <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。 -->
4、动态创建 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>
5、利用 http 接口请求下载后,创建 script 标签
<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>
更新日志
e7112
-1于