JavaScript高性能优化:脚本加载与执行策略详解
浏览器渲染HTML与执行JavaScript是单进程模式,脚本加载会阻塞页面渲染。本文详解通过调整script位置、合并文件、使用defer/async属性、动态创建script标签及XHR注入等五种策略,解决脚本阻塞问题,提升页面加载速度与用户体验。
JavaScript单进程模型与阻塞问题
浏览器在渲染HTML页面和处理JavaScript脚本时采用单进程模型。当解析到<script>标签时,浏览器会优先执行标签内的代码;若通过src属性加载外部文件,则需先下载再执行。在此期间,页面渲染和用户交互均会被阻塞。
尽管脚本加载会造成阻塞,但通过特定的优化手段可以有效降低其对性能的影响。
基础优化策略:位置与合并
1. 调整script标签的位置
当<script>标签放置在<head>部分时,例如:
<head>
<script type="text/javascript" src="js1.js"></script>
<script type="text/javascript" src="js2.js"></script>
<script type="text/javascript" src="js3.js"></script>
</head>
加载多个JS文件时,浏览器会因优先下载和执行JS代码而阻塞页面渲染,导致白屏现象。在浏览器解析到<body>标签之前,不会渲染任何页面内容,用户无法预览或交互,严重影响用户体验。
注意:
现代浏览器支持资源的并行下载,但这仅适用于外部<script>资源,且不会阻塞其他<script>标签的下载,但仍会阻塞其他类型资源的下载。JavaScript资源的下载是异步的,但执行过程是同步的,因此仍会造成阻塞。将<script>标签移至<body>底部,确保脚本执行前页面已完成渲染,是常见的JavaScript优化手段。
2. 合并多个script标签
浏览器解析HTML时,遇到<script>标签会因执行脚本产生延迟。对于带有src属性的外链脚本,延迟更为显著。过多的HTTP请求会增加性能开销,因此减少延迟是优化关键。通过合并多个JS文件,可以减少HTTP请求次数、三次握手次数及多余的HTTP头传输,从而降低响应时间并提升用户体验。网上有多种合并JS的方案和工具,此处不再赘述。
高级无阻塞加载方案
3. 采用无阻塞下载JavaScript的方法
- 利用script标签的defer和async属性
- 通过动态创建的script标签下载并执行JavaScript代码
- 使用XHR对象下载JavaScript代码并注入页面
defer与async属性详解
3.1. 使用script标签的defer和async属性
async和defer属性均用于异步加载JS文件,期间不会阻塞浏览器其他进程。区别在于:async在加载完成后自动执行,而defer需等待页面加载完毕后再执行。需注意,这两个属性仅在带有src属性的<script>标签(即外链脚本)中有效。以下是示例:
<!DOCTYPE html>
<html>
<head>
<title>defer example</title>
</head>
<body>
<script type="text/javascript" src="defer.js" defer></script>
<script>
alert("script");
</script>
<script>
window.onload= function(){
alert("load");
}
</script>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
<div class="demo">defer demo</div>
</body>
</html>
//defer.js文件仅包含alert("defer");一行代码 async的例子页面结构相同,此处省略示例,可参考下方链接。 defer example链接:[此处] async example链接:[此处]
虽然页面结构相同,但执行顺序不同:
打开defer.html,依次看到:弹出"script"的alert框 => 页面渲染文字 => 弹出"defer"的alert框 => 弹出"load"的alert框。 打开async.html,依次看到:弹出"script"的alert框 => 弹出"async"的alert框 => 页面渲染文字 => 弹出"load"的alert框。
动态Script标签加载
3.2. 使用动态创建的script标签来下载执行JavaScript代码
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "file.js";
document.getElementByTagName("head")[0].appendChild(script);
当file.js所在的script元素添加到页面时,下载即启动。此方式的优势在于,file.js的下载和执行不会阻塞页面其他进程。
从示例可明显看出,动态加载方式可在alert框弹出前显示页面文字,而普通方式需在alert框弹出后才能看到文字。
我们可以封装一个跨浏览器的函数,用于读取script脚本并动态创建script标签:
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("head")[0].appendChild(script);
}
此类动态加载脚本的方法兼容性良好且实现简单,是常用的无阻塞解决方案。
XHR异步加载与注入
3.3. 使用XHR对象下载JavaScript代码并注入页面
另一种无阻塞加载脚本的方式是利用XMLHttpRequest(XHR)对象获取脚本并注入页面。该技术首先创建XHR对象,下载JavaScript文件,随后通过动态创建<script>元素将代码注入页面。
var xhr = new XMLHttpRequest();
xhr.open("get","file.js",true);
xhr.onreadystatechange = function(){
if(xhr.readyState===4){
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);
}
}
}
上述代码发送GET请求获取file.js文件,通过onReadyStateChange检测readyState是否为4(表示请求完成)以及HTTP状态码是否有效(200表示有效响应,304表示读取缓存)。判断响应有效后,动态创建<script>标签,其内容为服务器返回的responseText。
该方法的优缺点如下:
优点:下载JavaScript代码可不立即执行,且兼容性良好,适用于所有主流浏览器。 缺点:JavaScript文件必须与请求页面处于同一域,因此无法从CDN下载,不适合大型Web应用。
推荐无阻塞方案
4. 推荐的无阻塞方案
若页面需添加大量JavaScript代码,可先在页面中引入之前封装好的动态读取script脚本的函数loadScript,随后利用它加载其他所需脚本,例如:
<script type="text/javascript" src="loader.js"></script>
<script type="text/javascript">
loadScript("file.js",function(){
//do something
});
</script>
这样,仅在第一个<script>下载精简的loader.js文件时对页面产生轻微影响,后续<script>标签的影响则大幅降低。
优化JS回到顶部性能:防抖节流与requestAnimationFrame应用
2026-11-05 16:33:51
JS中Array、Set与Map的区别及应用场景选择
2026-11-05 16:03:02
PixiJS 常见报错排查:纹理跨域、黑屏与内存泄漏解决
2026-11-05 10:05:21
JS数组find与filter取值区别及性能对比
2026-11-05 08:39:21
通过添加时间戳参数强制刷新JS资源
2026-11-05 08:32:36
