从文科生到前端:我在性能优化路上踩过的坑与攒下的经验
大家好,我是一个非科班出身、大学读的是汉语言文学的“野生”前端。三年前,我还在纠结“赋比兴”的修辞手法,如今却在 Chrome DevTools 里盯着 Lighthouse 报告发愁。转码这条路,说不后悔是假的——但每次看到自己写的动画丝滑跑起来,或者线上性能指标提升一截时,又觉得:嗯,这班加得值。
今天想和大家聊聊这几年在技术探索和实践中的一些真实体会,尤其是围绕性能优化这个永不过时的话题。这不是一篇教科书式的指南,而是夹杂着焦虑、加班、面试题挑战和深夜看开源源码的碎碎念。如果你也和我一样,是从非技术背景杀进前端圈的“异类”,或许会有点共鸣。
面试题挑战?不如说是日常工作的复刻
去年秋招开始前,我刷了不少前端面试题。其中一道高频题是:“如何优化首屏加载速度?”
当时我背了一堆答案:懒加载、代码分割、预加载、CDN、压缩……看起来头头是道。但真回到公司项目里,才发现纸上谈兵和实战之间隔着一整个银河系。
我们组负责的是一个企业级 SaaS 后台系统,用户量不大,但页面复杂度高——表单嵌套弹窗,弹窗里还有图表,图表还得带动画。产品经理还总爱说:“这个交互要像 Figma 那样流畅!”(内心OS:Figma 是用 C++ 写的,我们是 React + Ant Design 啊大哥!)
去年双11期间,客户突然反馈“页面卡成PPT”,老板直接拉群@所有人:“今晚必须搞定。”
那一刻,我突然意识到:性能问题从来不是面试题里的抽象概念,而是线上用户的真实怒火。
于是,我带着“被逼上梁山”的悲壮感,开始了为期两周的性能攻坚。
性能优化不是魔法,而是一步步拆解
很多人以为性能优化就是加个 React.memo 或者开个 Webpack 的 splitChunks。但现实是,优化的第一步,永远是测量。
我先用 Lighthouse 跑了一遍当前页面:
| 指标 | 优化前 | 目标 |
|---|---|---|
| First Contentful Paint (FCP) | 4.2s | <2s |
| Largest Contentful Paint (LCP) | 6.8s | <2.5s |
| Total Blocking Time (TBT) | 980ms | <200ms |
看着这些数字,我一度怀疑是不是网络抽风了。但反复测试后确认:这就是我们的真实水平。
更扎心的是,我们的 Bundle Size 高达 3.7MB(未压缩),光 lodash 就占了 70KB——而且只用了 debounce 和 throttle!
1. 包体积瘦身:砍掉那些“我以为会用”的依赖
第一步:删!
- 把
moment.js换成dayjs,Bundle 减少 200KB+ - 用原生
Array.prototype.filter替代lodash.filter - 移除一个“未来可能用到”的 UI 库(结果三年了也没用上)
然后开启 Webpack 的 splitChunks,把第三方库和业务代码分离:
// webpack.config.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
antd: {
test: /[\\/]node_modules[\\/]antd/,
name: 'antd',
chunks: 'all',
}
}
}
}
效果立竿见影:首屏 JS 体积从 2.1MB 降到 1.3MB,FCP 直接降了 1.5s。
小插曲:运维同事看到新构建产物后问我:“你删了什么?怎么 CDN 流量少了 40%?” 我笑而不语,心里默默感谢那个劝我学 Webpack 配置的 GitHub issue。
动画卡顿?别急着 blame 浏览器
作为对前端动画特别感兴趣的“文科生”,我一直痴迷于微交互动效。但有一次,我给一个数据表格加了个“行展开动画”,结果 QA 直接提了个 P0 级 Bug:“低端机上滚动直接卡死”。
我一开始甩锅给 CSS 动画性能差,后来用 Performance 面板录了一下,发现瓶颈根本不在动画本身——而是在每次展开都触发了整张表格的重新渲染!
原来,我用的组件状态是全局共享的,一个 useState 变更导致父组件 rerender,连带所有子行一起更新。即使加了 React.memo,因为 props 引用变了,缓存也失效。
解决方案?拆分状态粒度 + 使用 useCallback 固化函数引用。
// 优化前:所有行共用一个 expandedKeys 状态
const [expandedKeys, setExpandedKeys] = useState([]);
// 优化后:每行有自己的 expand 状态(通过 context 或独立组件管理)
const Row = ({ rowId }) => {
const [expanded, setExpanded] = useState(false);
const toggle = useCallback(() => setExpanded(v => !v), []);
// ...
};
同时,动画改用 transform + will-change: transform,确保走 GPU 加速:
.expanded-row {
will-change: transform;
transition: transform 0.3s ease;
transform: scaleY(1);
}
.expanded-row.collapse {
transform: scaleY(0);
}
最终,在千元安卓机上也能流畅展开/收起。产品经理终于露出了满意的笑容(虽然第二天又提了个新需求:“能不能加个弹簧效果?” 😭)。
开源源码:我的“野路子”学习法
作为一个没有系统学过计算机的人,我特别依赖阅读开源项目源码来补课。比如为了搞懂 React 的调度机制,我硬啃了 scheduler 包的源码;为了优化动画,去翻了 framer-motion 的实现。
最近研究 react-window 时,发现它用“虚拟滚动”只渲染可视区域的 DOM 节点,大幅减少内存占用。我们有个日志列表页,动辄上千条记录,之前全量渲染直接卡爆。引入 react-window 后,内存占用从 400MB+ 降到 80MB,滚动帧率稳定在 60fps。
但别以为照搬就行。有一次我直接 copy 了一个开源项目的防抖逻辑,结果因为没处理 this 绑定,在 class 组件里报错 Cannot read property 'setState' of undefined。调试到凌晨两点,才发现人家用的是箭头函数,而我还在用老式方法声明……
教训:看源码不是为了复制粘贴,而是理解设计思想。就像我大学老师说的:“读《红楼梦》不是为了抄里面的诗词,而是学曹雪芹怎么写人。”
技术分享:从“不敢开口”到主动输出
刚入行时,团队每周有技术分享会。轮到我时,我紧张得手抖,讲了个“CSS 居中方案大全”,结果被同事问:“你知道 Flexbox 的 align-content 和 align-items 区别吗?” 我当场懵了。
但后来我发现,分享是最好的学习方式。每当我准备一次分享,就必须把知识点吃透,还要考虑听众会不会听懂。这种“输出倒逼输入”的模式,让我进步飞快。
上个月,我在公司内部做了一场《前端性能优化实战:从 Lighthouse 到用户感知》的分享,重点讲了我们怎么把 LCP 从 6.8s 压到 2.1s。没想到会后好几个后端同事跑来问:“你们前端现在这么卷了吗?” 还有实习生私信我索要 PPT。
其实哪有什么“卷”,不过是被线上事故逼出来的生存技能罢了。
跳槽前的反思:技术深度 vs 广度
在这家公司三年多,我从只会写 HTML/CSS 的小白,到现在能独立负责性能优化专项。但最近也在思考:是不是该换个环境了?
一方面,当前业务趋于稳定,技术挑战变少;另一方面,面试时越来越多人问“你对前端工程化的理解”、“如何设计可维护的动画系统”这类偏架构的问题。而我更多是“救火队员”角色,缺乏从 0 到 1 搭建体系的经验。
所以最近一边刷 LeetCode(文科生的痛),一边研究 Vite 插件机制、Web Vitals 监控方案。甚至尝试用 Rust 写了个简单的 WASM 性能分析工具(虽然只跑了 “Hello World”)。
说真的,每次投简历看到“要求 3 年以上经验,精通性能优化、工程化、跨端方案……”时,我都想回一句:“您这要求,是要招一个人,还是招一个团队?”
但吐槽归吐槽,该学还得学。毕竟在这个行业,不进则退。
最后一点真心话
作为非科班出身的人,我深知自己基础薄弱。算法不会?补!网络协议不懂?学!但我也渐渐明白:前端的价值,不只是写代码,更是连接用户与产品的桥梁。
性能优化也好,动画交互也罢,最终目标都是让用户“感觉快”、“用得爽”。有时候,一个 loading skeleton 比 SSR 还能提升感知性能;一个合理的加载顺序,比代码分割更能留住用户。
所以,别被面试题吓住,也别被“大厂标准”绑架。从实际问题出发,小步快跑,持续迭代——这才是我们普通开发者的真实路径。
对了,上周五晚上加班时,我又改了一版动画缓动函数。测试通过那一刻,窗外已经天亮了。但看到那个按钮以恰到好处的弹性弹出来时,我笑了。
——这大概就是,文科生写代码的浪漫吧。
附:常用性能优化 Checklist(自用版)
- Bundle 分析(webpack-bundle-analyzer)
- 关键资源预加载(
<link rel="preload">) - 图片懒加载 + WebP 格式
- 避免强制同步布局(Forced Synchronous Layout)
- 使用
content-visibility: auto(谨慎) - 长列表虚拟滚动
- 防抖/节流高频事件
- 避免在 render 中创建新对象/函数
- 监控 Core Web Vitals(通过 web-vitals 库上报)
如果你也有类似的“野路子”经历,欢迎留言交流。毕竟,在这个圈子里,谁还不是个“自学成才”的打工人呢?

评论 0