JavaScript高性能优化:脚本加载与执行策略详解

作者:寒星时间:2026-10-29 21:11:59浏览:482

浏览器渲染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的方法

  1. 利用script标签的defer和async属性
  2. 通过动态创建的script标签下载并执行JavaScript代码
  3. 使用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>标签的影响则大幅降低。

作者最新文章