咱们直接进入正题。前端这行当,写了几年JavaScript之后,你迟早会遇到那个让你抓狂的瞬间:页面在本地开发时丝般顺滑,一上线、数据一多、用户一访问,帧率掉成PPT,点击按钮半天没反应,滚动跟拖泥带水似的。这个标题里的"JavaScript性能优化实战"不是让你背几条优化原则,而是要解决实际项目里那些肉眼可见的卡顿、白屏、响应慢问题。本文会从测量手段、网络加载、渲染机制、内存管理、移动端特性和工程化落地这几个维度展开,里面所有的方法、工具和坑,都是我这些年真刀真枪在项目里验证过的,可以直接用到你的代码里。
文章主要适合这几类人:写了一些业务代码、但觉得性能优化无从下手的前端工程师;正在被移动端H5卡顿困扰的开发者;还有做前端基建、需要搭性能监控体系的同学。底层原理我会讲透,实操步骤我会给全,保证你能照着做、能复现、能验证。
1. 先确定"慢"在哪:性能优化的起点不是写代码,而是测量
很多人的优化思路是:我猜这里慢,改一下;我猜那里卡,调一下。结果代码改了一堆,用户体感毫无变化。性能优化第一步不是改代码,而是搞清楚瓶颈到底在哪。这个道理说起来简单,实际执行的时候大部分人还是忍不住直接动手"优化"。你问十个人性能优化从哪开始,八个会说是压缩图片、合并请求、减少DOM操作。但真实情况是,你怎么知道该压缩这些?那是因为你已经被灌输了一堆"优化手段",但你忽略了一个关键问题:这些手段对应你自己的项目,真的有效吗?
1.1 别拿直觉当依据:为什么性能问题要从测量开始
举个我实际遇到的例子。之前有个后台管理系统,用户反馈切换菜单的时候特别卡。按照当时我的第一直觉,下意识就会去查菜单组件是不是渲染的东西太多了。结果一测Performance面板,发现耗时大头根本不在渲染,而是每次切换菜单时,某个全局mixin会触发一次深拷贝的配置对象,这个对象包含了一张巨大的权限表,每次深拷贝耗时将近200毫秒。真正拖慢性能的是这个,而不是你辛辛苦苦优化的列表渲染。如果不测量,我大概率会去优化组件结构,费半天劲,用户依然卡成狗。
这就是为什么业内常说"测量先行"。不过测量也有测量的问题:Performance面板录制的数据,很容易把本地网络、后台接口波动这些噪音也算进去。我一般在用DevTools测量前,会先做两件小事:一是把网络环境切到Slow 4G或自定义的弱网,保证网络延迟不会掩盖JS执行时间的问题;二是用Chrome的无痕窗口打开页面,排除浏览器插件对性能的干扰。这两个操作能显著提升性能数据的可信度。
1.2 浏览器DevTools的Performance面板实测指南
Performance面板(Chrome DevTools里带火焰图的那个)是定位运行时性能问题的核心工具,但很多人只会录一段,然后看看红色的长条在哪就完了,完全没发挥出它的价值。我录制的步骤一般是这样的:
- 打开DevTools切到Performance面板,点击左上角的圆点开始录制。
- 在页面上执行你关心的交互,比如点击、滚动、切换路由。
- 点Stop,等待分析结果生成。
- 首先看顶部Overview区域的FPS(帧率)曲线。如果出现明显的红线,说明这一步交互掉帧了。
- 然后看Main轨道,找到那段交互对应的长任务(Long Task)。长任务指的是执行时间超过50毫秒的任务,浏览器规范里明确说超过50毫秒的任务会阻塞主线程,导致用户感觉卡顿。
- 点开这个长任务的火焰图,从下往上数,真正耗时的函数会被标成深色,往往就是你该优化的点。
实操里有个很反直觉的地方:火焰图里宽的函数不一定是最该优化的。宽说明它执行时间长,但如果你能看到某个函数在火焰图里反复出现、堆叠成很多层,那么整体时间可能更值得关注。因为它被调用了几千次,虽然单次不慢,但累计时间长,这时可以考虑缓存它的计算结果。
1.3 用Lighthouse给性能打分的实操解读
如果你的项目还处在"跟性能有关的指标一塌糊涂"的阶段,我建议先跑一次Lighthouse,拿到一个全局的基线分,再对比优化后的得分,这样有数据支撑,跟产品和老板也好沟通。但这个分数有很多容易误读的地方,我说几个常见坑。
Lighthouse在移动端模拟环境下跑出来的Performance得分,其实是一个综合了FCP(首次内容绘制)、LCP(最大内容绘制)、TBT(总阻塞时间)、CLS(布局偏移)等指标的加权分。我自己实际跑过的经验是,这个分数对"JS执行时间"的敏感度很高。如果你的首屏加载了一半时间都花在解析、编译、执行JS上,哪怕你的网络是宽带级别的,Lighthouse也能把你拉到非常低的分数。所以当你看到Performance分数低,第一反应不应该是"我换个更强的服务端",而是打开面板里的Diagnostics,看看是不是JS执行时间太长。
还有一个容易忽略的点:Lighthouse适合做中长期回归对比,不适合做局部优化的微调验证。因为它每次跑分都受网络波动影响,同一页面连续跑两次相差5到10分都很正常。所以我一般在做优化验证时,会连续跑三次,取中间值或中位数来对比,如果项目比较大的话,直接用性能监控系统的数据更稳。
1.4 Long Tasks和INP:影响用户感知的关键指标
几年前大家还在聊TTI(可交互时间),最近一两年,Chrome团队已经把INP(Interaction to Next Paint,交互到下一帧绘制的时间)提到了很高的优先级,并且在2024年3月把INP正式替换了FID(First Input Delay),成为Core Web Vitals里的核心指标之一。为什么会有这个变化?因为FID只统计第一次输入到浏览器响应之间的延迟,衡量的是"页面首次能不能快速响应"。但用户的真实体验是全过程的,比如页面已经加载完了,用户开始正常操作了,这时候你的按钮每点一次都要卡一会儿,那体验照样糟糕。INP衡量的是整个页面生命周期内所有交互的延迟,取最差的那个近似值。
我在项目里做性能监控的时候,指标采集主要就看两个:一是LCP看加载快不快,二是INP看交互跟不跟手。INP的临界值一般不超过200毫秒是良好,200到500毫秒需要改进,超过500毫秒
