JavaScript 性能优化实战:从加载到执行的全链路指南

作者:安全边界

时间:2026-10-29

浏览:474

本文系统梳理了 JavaScript 性能优化的核心策略,涵盖脚本加载位置与合并、DOM 操作与重排重绘控制、变量作用域对访问速度的影响以及循环与二进制运算的优化技巧。通过具体代码对比,帮助开发者提升页面渲染效率与执行性能。

文章目录

JavaScript 脚本加载策略

一、关于 JavaScript 文件的加载位置

在 HTML 文档中,<script> 标签可置于 <head> 或 <body> 区域。鉴于 JavaScript 执行与 UI 渲染共享单线程,若 JS 文件加载会阻塞后续页面解析,浏览器需等待 JS 完全加载并执行完毕后才继续后续操作。此机制可能导致页面出现空白或卡顿。前端开发不仅需实现功能,更应关注优质用户体验。为消除用户等待的枯燥感,针对此问题提出两种解决方案:

1. 若 JS 文件无特殊要求在页面渲染前必须载入及编译完成,建议将 JS 文件置于 </body> 标签之前(即所有页面呈现内容之后),而 CSS 文件仍保留在 <head> 区域(避免用户看到布局混乱的页面)。此举可让用户先看到有布局的页面而非空白页。针对数据需通过 JS 请求加载的情况,可对数据加载顺序进行排序:急需呈现的接口优先执行,非关键接口延后执行,并配合简单的载入动画或提示。

2. 若 JS 文件指定需先执行以更好展示页面内容,可在首个 JS 或页面初始阶段添加载入小动画,如有趣或可爱的动画场景。此举能有效缓解用户等待时的无聊感,甚至可能因动画趣味性提升项目用户体验。

最终推荐:将 <script> 标签尽可能放置在 </body> 标签之前加载,以优化用户体验。

二、关于 JS 文件的合并 在团队开发中,通常将不同功能的代码块分别置于不同的 JS 文件中,以便多人协作开发时更易定位对应文件夹或文件,而非在长文件中查找方法。这确实能提高团队开发效率及新人二次开发和维护的便利性。然而,从页面性能角度审视,这正是问题所在。书中指出:Each HTTP request brings with it additional performance overhead,so downloading one single 100 KB file will be faster than downloading four 25 KB files.

下载 1 个 100KB 的文件比下载 4 个 25KB 的文件更快,而开发过程中区分各个文件又有诸多好处,因此合并操作通常安排在开发完成后进行。相信此操作大家并不陌生,当前前端工具丰富,可根据习惯选择相应的压缩工具。 此处简要提及,在文件加载方面还可利用 defer 和 async 属性,分别用于延迟加载和异步加载。现代浏览器大多已支持 defer 属性,若尚未习惯使用且不确定是否存在潜在问题,有兴趣者可自行 Google 相关知识点,此处仅作简要提及。

当前主流框架大多已配合懒加载和按需加载机制。

三、实现更快速的数据访问

对于浏览器而言,标识符所处的作用域层级越深,读写该标识符的速度越慢(原型链查找机制亦遵循此规律)。这一点易于理解,简单比喻为:杂货店距离家越远,购买酱油所花费的时间就越长…… 熊孩子,打酱油耗时过长,菜早已烧焦 -.-~

若需在当前函数内多次使用某变量值,可先将其存储于局部变量中,案例如下:


 //修改前
 function showLi(){
   var i = 0;
   for(;i<document.getElementsByTagName("li").length;i++){  //一次访问document
     console.log(i,document.getElementsByTagName("li")[i]); //三次访问document
   };
 };
 //修改后
 function showLi(){
   var li_s = document.getElementsByTagName("li"); //一次访问document
   var i = 0;
   for(;i<li_s.length;i++){
     console.log(i,li_s[i]); //三次访问局部变量li_s
   };
 };

DOM 操作与渲染优化

四、DOM 操作的优化

众所周知,DOM 操作比 JavaScript 代码执行更消耗性能。尽管无法完全避免 DOM 操作,但可尽量降低其对性能的消耗。

通过以下代码示例阐释该问题:


 function innerLi_s(){
   var i = 0;
   for(;i<20;i++){
     document.getElementById("Num").innerHTML="A"; //进行了20次循环,每次又有2次DOM元素访问:一次读取innerHTML的值,一次写入值
   };
 };

针对上述方法进行的改写示例:


 function innerLi_s(){
   var content ="";
   var i = 0;
   for(;i<20;i++){
     content += "A"; //这里只对js的变量循环了20次
   };
   document.getElementById("Num").innerHTML += content; //这里值进行了一次DOM操作,又分2次DOM访问:一次读取innerHTML的值,一次写入值
 };

五、减少 DOM 的重绘与重排版

元素布局改变、内容增删改或浏览器窗口尺寸变化均会导致重排(Reflow);而字体颜色或背景色修改则导致重绘(Repaint)。 对于类似以下代码的操作,据称现代浏览器大多已进行优化(将其优化为仅执行 1 次重排版):


 //修改前
 var el = document.getElementById("div");
 el.style.borderLeft = "1px"; //1次重排版
 el.style.borderRight = "2px"; //又1次重排版
 el.style.padding = "5px"; //还有1次重排版
 //修改后
 var el = document.getElementById("div");
 el.style.cssText = "border-left:1px;border-right:2px;padding:5px"; //1次重排版

针对多重操作,以下三种方法亦可减少重排版和重绘的次数:

1. DOM 先隐藏,操作后再显示:涉及 2 次重排(临时设置 display:none)

2. 使用 document.createDocumentFragment() 创建文档片段进行处理,操作完成后追加至页面:涉及 1 次重排

3. var newDOM = oldDOM.cloneNode(true) 创建 DOM 副本,修改副本后通过 oldDOM.parentNode.replaceChild(newDOM,oldDOM) 覆盖原 DOM:涉及 2 次重排

循环与底层运算优化

六、循环的优化

这应是较多开发者已熟知的写法,此处简要带过(后续仍将以代码加注释形式说明)~


//修改前
 var i = 0;
 for(;i<arr.lengthli++){ //每次循环都需要获取数组arr的length
   console.log(arr[i]);
 }
 //修改后
 var i = 0;
 var len = arr.length; //获取一次数组arr的length 
 for(;i<len;i++){
   console.log(arr[i]);
 }
 //or
 var i = arr.length;;
 for(;i;i--){
   console.log(arr[i]);
 }

七、合理利用二进制操作

例如:对 2 取模,偶数最低位为 0,奇数最低位为 1。与 1 进行位与(AND)操作时,结果为 0 表示偶数,结果为 1 表示奇数。

代码如下:


 .odd{color:red}
 .even{color:yellow}

  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
  </ul>

 var i = 0;
 var lis = document.getElementsByTagName("li");
 var len = lis.length;
 for(;i<len;i++){
  if(i&1){
   lis[i].className = "even";
  } else{
   lis[i].className = "odd";
  }
 };

尽管现代浏览器性能已大幅提升,但作者认为这是提升代码质量的一种追求。可能单个或少数几个优化点不会带来显著的性能影响,但从多个维度进行优化后,可能产生质的飞跃。

以上总结的 JavaScript 提高性能知识点,希望大家能够熟练掌握。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章