引言
功能能跑通,只是第一步。当用户变多、数据变大、交互变复杂,性能问题就会浮现:页面卡顿、响应迟钝、滚动掉帧。这时候,优化就不是可选项,而是必须做的事。这篇文章不带代码,从思路到方法,把 JS 性能优化讲清楚。
一、优化的前提:先测量
很多人一提到优化,就凭直觉开始改代码。这是大忌。
没有测量,就没有优化。你以为是瓶颈的地方,可能根本不是;你忽略的角落,可能才是元凶。
正确的做法是:先用工具测量,找出真正的耗时点,再针对性地改,改完再测,确认有没有效果。
浏览器提供了性能面板,可以记录一段时间内的执行情况,看清楚哪些函数占用时间最多、哪些操作触发了重排重绘。还有网络面板,看资源加载耗时。这些工具是优化的起点。
二、减少不必要的计算
最直接的优化,是别做无用功。
缓存重复计算的结果。同一个值,如果算一次很贵,而它又不会变,那就存起来,下次直接用。这在递归、复杂运算、频繁调用的函数里效果显著。
提前退出。循环或函数里,一旦条件满足就尽早返回,不要继续做后面的判断。减少无效执行。
缩小处理范围。只处理需要处理的数据,不要每次都遍历整个列表。能用索引定位的,不要全表扫描。
三、控制执行频率
有些操作本身不慢,但触发太频繁,累积起来就成了问题。
滚动、缩放、鼠标移动、输入框输入,这些事件每秒可能触发几十上百次。如果每次触发都执行一段较重的逻辑,页面就会卡。
解决办法是降低执行频率。有两种常用策略:
一种是防抖:事件停止触发后,等一段时间再执行。适合搜索联想、表单校验这类场景——用户还在打字,没必要每次都算。
另一种是节流:固定时间内最多执行一次。适合滚动加载、拖拽这类需要持续响应,但不能太频繁的场景。
两种策略目标一致:把高频触发降成低频执行,减轻负担。
四、减少对页面的影响
JS 操作页面,会触发浏览器的重新计算和重新绘制。这个过程很贵,频繁触发就会卡。
批量修改。不要改一点就渲染一次,而是把多次修改攒起来,一次性应用。浏览器自身也会做批处理,但主动权在开发者手里更可靠。
避免强制同步布局。读取某些布局属性时,浏览器必须立刻计算最新布局。如果你先改样式、马上读布局、再改样式,就会反复触发计算,性能极差。正确的做法是:先读完所有需要的布局信息,再统一修改。
减少重排范围。改动影响的范围越小越好。改一个元素的颜色,比改它的尺寸影响小;改尺寸,又比增删元素影响小。能用合成层处理的动画,尽量交给合成层,不要走布局流程。
五、合理使用异步
别让主线程干重活。主线程负责渲染和响应交互,如果被一个耗时任务占住,页面就冻住了。
对于计算密集的任务,可以考虑放到后台线程去跑,主线程只管接收结果。这样页面始终保持流畅。
对于网络请求、文件读取这类等待型任务,用异步方式,不要同步阻塞。等待期间,主线程可以继续处理其他事。
六、资源加载优化
性能不只取决于代码执行,还取决于资源什么时候到。
减少请求数量。每个请求都有开销,能合并的合并,能内联的内联。
减小资源体积。压缩、去除无用代码、按需加载。用户当前不需要的,不要一开始就下载。
利用缓存。不变的资源长期缓存,变化的资源用版本号控制。第二次访问时,大部分资源直接从本地拿,速度快很多。
懒加载。图片、视频、非首屏模块,等快要用到时再加载。首屏只加载必需的,用户感知到的速度会明显提升。
七、数据结构与算法的选择
有时候,性能问题不是写法问题,而是选错了工具。
用数组做频繁查找,不如用映射结构;用嵌套循环处理大量数据,不如先排序或建索引;频繁增删的场景,链表可能比数组更合适。
这些选择在数据量小的时候看不出差别,一旦数据上万,差距就是几十倍甚至上百倍。
所以,了解常见数据结构的特点和适用场景,是性能优化的重要基础。
八、不要过早优化
最后要说一个反面提醒:不要过早优化。
在没搞清楚瓶颈在哪之前,就花大量精力去做微优化,往往得不偿失。代码可读性下降,维护成本上升,性能却没提升多少。
正确的顺序是:先写清楚、写正确,再测量,再优化。优化要有数据支撑,要有明确目标,改完要验证效果。
可读性和性能之间,大多数时候应该优先可读性。只有在确实影响用户体验的地方,才值得为性能牺牲一点可读性。
九、总结
性能优化不是玄学,而是一套有章可循的方法:先测量找瓶颈,再减少计算、控制频率、降低渲染影响、合理异步、优化加载、选对结构,最后避免过早优化。掌握这套思路,你就能在程序变慢时有的放矢,而不是盲目猜测。这是从"能实现功能"到"能交付好产品"的关键一步。