我在这行干了快十年,回头看看,真正值钱的往往不是某个框架的 API,而是那些散落在日常开发里的判断力和兜底能力。最近整理工作笔记,把前端相关的零散知识点重新过了一遍,今天把这份随记整理出来。
这篇随记不按教科书顺序讲,而是按我实际踩坑的顺序来:性能优化、架构选型、工程化硬骨头、AI 时代的定位、面试高频考点、前后端联调。适合工作一两年的前端同学系统补短板,也适合准备面试的朋友拿来当复盘清单。内容里涉及的东西比较杂,但每一条都是我遇到并解决过的问题,你直接拿过去用不会跑偏。
1. 前端性能优化:先解决那 80% 的可见问题
1.1 首屏加载的真相:Bundle 体积是原罪
写性能优化,我一般不看代码,直接开 DevTools 看 Network。很多项目卡,不是代码写得差,是发版打出来的 JS 太大。一个页面加载 3MB 的 app.js,再好的代码也救不回来。
我通常用三个维度判断首屏问题:bundle 体积、请求链长度、关键渲染路径。先解决第一个。如果一个项目引入了 element-plus,但是没有按需引入,打包工具会把所有组件塞进去。我见过一个后台项目,光 UI 库就占 1.2MB,后来改成 unplugin-vue-components 自动按需导入,瞬间少了 700KB。这里有个细节:按需引入不只是写 import { ElButton } from 'element-plus' 这么简单,组件库的样式也得同步按需加载,否则样式还是全量引入。用 unplugin 的 resolver 可以一并搞定。
路由懒加载也是老生常谈,我把它当成一条硬性规范:非首屏的路由组件,一律 () => import() 拆出去。这样首屏只加载当前页面的代码,其他路由等用户跳转了再拉。拆完你会发现 vendor 里还躺着 axios、lodash、dayjs 这些公共库,它们属于长期不变的依赖,可以单独打成 vendor chunk,利用浏览器缓存减少重复下载。
1.2 图片、字体和静态资源:细节里藏着大头
然后说图片。很多团队没有明确的图片规范,设计给什么就放什么,于是首页轮播图一张 2MB 的 PNG 直接丢进去。以前我优化过一个列表页,整页 40 张缩略图,每张都是 200KB 的 PNG,改成 WebP 并且压缩后,总大小从 8MB 降到 1.2MB,加载时间直接砍半。
现在这个时间点,图片处理我推荐的原则很简单:
- 内容图片优先用 WebP 或 AVIF,要兼容就通过
<picture>标签做降级; - 插图类和图标类尽量走 SVG 雪碧图或 iconfont,不用位图;
- 首屏以下的图片一律懒加载,
loading="lazy"原生支持,不要自己写插件; - 小图(一般小于 3KB)可以直接内联成 base64,省一次请求,但大图千万别内联,HTML 体积会爆炸。
字体也一样。字体文件经常被人忽略,一个中文字体动辄几 MB。如果只是界面按钮和标题用特殊字体,可以只提取需要的字,用 fontmin 之类工具做子集化。再有就是 font-display: swap,避免字体加载期间页面文字不可见。
1.3 运行时性能:列表渲染和重排重绘的小账
首屏加载解决之后,剩下的就是交互卡顿问题。常见的元凶是巨量 DOM 渲染和不必要的重排重绘。
比如一个数据大屏页面,后端每秒推送一次数据,前端拿到 500 条然后直接 v-for 渲染表格。改用虚拟滚动之后,DOM 节点数从 500 骤降到 30 左右,滚动丝滑。虚拟滚动实现起来也不复杂,核心就是固定行高加容器滚动偏移量计算。如果不想自己写,用现成的虚拟列表组件也行,但最好理解一下原理,面试的时候经常被问。
重排重绘的问题则更多是“无意识”踩中的。频繁读取 offsetWidth、clientHeight,然后又修改样式,会造成强制同步布局。性能差的页面能把主线程卡到几十毫秒一帧。我的经验是:把读取和写入分开做,需要连续读取就在一个循环里读完,需要连续写入就集中改 class 而不是逐个改 style,让浏览器自己做批量处理。
性能优化的第一步不是猜,而是测量。我用 Lighthouse 做粗略体检,再用 Performance 面板抓真实交互的耗时。很多团队优化了半天,连优化前基线都没有,后面改没改都不知道。建议平时把性能预算加到 CI 里,比如用 webpack-bundle-analyzer 检查体积超过阈值就报警,或者用 lighthouse-ci 在合并请求前自动跑分。用数据说话,性能优化才不会变成玄学。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 组件库与架构选型:别让技术栈拖累业务
2.1 自研组件库还是使用现成组件库
前端团队经常纠结一个问题:组件库用现成的还是自己搞一套?
我的观点很明确:默认用现成的,除非你有明确理由。element-plus、antd、vant 这些组件库背后有大量用户踩过的坑,稳定性远超小团队自研。自研组件库意味着你要维护表单校验、弹窗、日期选择器、表格、树表这些高复杂度组件,光是一个表格的分页、排序、多选、行展开组合起来就够你折腾半年。
但有一种情况必须自研——业务里有大量定制化场景,现成组件库的二次开发成本已经高于重写的成本。比如低代码平台,流程编排、属性面板这些很难用通用组件覆盖。这时候可以走两条路:要么在现成组件库上做封装,暴露自己的 API;要么从零构建一套轻量组件,只覆盖业务需要的。我建议除非有必要,否则不要“全量自研”,做“骨架加核心组件自研加生态兼容”是更现实的折中。
还有一个容易忽略的判断标准:团队规模和维护能力。三个人,各自还有业务需求要排期,此时自研一套组件库,后面基本就是烂尾。先把业务做起来,等问题真的痛了,再考虑投入自研,不迟。
2.2 字典管理这类系统功能,到底有用在哪
热词里看到“前端系统管理下的字典管理一般有啥用”,这个我得说说。很多新手看到后台系统的“字典管理”菜单,以为就是弄个下拉框数据,没什么特别。其实字典管理的核心是把业务中的固定枚举值从代码里抽出来,变成可动态配置的数据。
举个例子,一个审批系统里有“审批状态”:待审批、已通过、已驳回、已撤销。如果每个下拉框、每个表格列都硬编码这些值,后端返回 1/2/3/4,前端到处映射,改动一个状态文案就要全局搜。有了字典管理,前端根据字典类型请求接口拿配置,页面里统一通过 dictTransform 之类的过滤器渲染。好处是:
- 业务人员可以自己维护枚举值,不用发版;
- 多端(PC、移动、数据大屏)共享同一套字典配置;
- 后端返回的 code 和前端渲染的 label 解耦,新增状态不用改前端代码。
我做过一个多系统项目,光性别、地区、学历、职业这些字典就有 100 多个。没有字典管理的话,每个系统都要复制一遍映射关系,想想就恐怖。字典管理在不同团队实现不同,有的是后端接口返回,有的做成前端静态配置,但核心思想一致:集中管理,动态获取,统一渲染。
2.3 微前端的沙箱机制到底在隔离什么
微前端这几年很火,但它的核心难点不在路由分发,而在沙箱隔离。Vue3 和 React 通过微前端沙箱机制整合,通常指的就是 JavaScript 运行环境和 CSS 样式环境的隔离。
先说 JS 沙箱。子应用会在 window 上挂载自己的全局变量和事件监听,主应用也有自己的,如果不隔离,两个应用就会互相污染。常见的实现方式有两种:一种是用 Proxy 代理全局对象,把子应用对 window 的读写转发到一个虚拟对象上,类似修改 with 配合 Proxy 实现;另一种是快照恢复,应用切走时保存现场,切回来时恢复现场。实现细节有点绕,但你只需要理解:微前端框架是在帮你维护一套“全局变量快照”,让每个子应用以为自己是唯一运行的应用。
CSS 隔离就更直接了。进入子应用时,会给它的样式包一层作用域前缀,或者在容器 DOM 上做命名空间限制。老牌的 qiankun 用的是类似“样式转换加运行时隔离”的手段,但也不是绝对的,偶尔有遗漏的 body 级样式把主应用带歪。踩过这种坑之后,我学到的经验是:子应用样式尽量少写 body、html 级别的全局选择器,否则微前端框架累死也给你清不干净。
还有一点,微前端不是银弹。如果团队只有两三个前端,要搭建微前端体系,前期配置成本和维护成本反而是负担。我自己更推荐“先上单仓多包,把模块边界拆清楚,再考虑微前端”。微前端适合的场景是:多个独立团队开发维护不同模块,需要独立发版、独立部署,但又要聚合到一个界面里给人用。
3. 工程化里的几个硬骨头:大文件上传、Worker 与公式解析
3.1 大文件上传:切片、断点和并发控制的整套逻辑
“前端使用 worker 上传大文件”这个热搜特别真实,大文件上传是前端工程化里绕不开的题。它的核心思路就三个字:先切片。把一个 1GB 的文件切成 10MB 的小片,逐片上传,上传完成之后服务端再合并成原文件。
切片大小不是随便定的。切得太小,请求数爆炸,每个分片都有网络开销;切得太大,断点续传的粒度就粗,失败重传的代价大。我自己习惯按文件大小动态切:1GB 以下切 10MB,1GB 以上切 20MB。整体请求并发数控制在 3 到 5 个,避免把浏览器连接池打满。
断点续传要靠文件指纹来确定“哪些片已经传过了”。在前端用 spark-md5 或 crypto.subtle 算整个文件的 MD5,文件名加大小加指纹作为唯一标识。上传前先发一个“查询”请求,后端返回已有分片列表,前端只传缺失的部分,这就是断点续传的完整链路。
然后是进度和错误恢复。每个分片上传失败,要能自动重试,重试次数和间隔要有上限,别无限重试把接口打崩。整体进度则是已传分片数除以总分片数,注意要把“跳过已存在分片”的场景算进去,不然进度条会卡住不动。
3.2 Worker 在文件处理和复杂计算里的用法
为什么要把上传操作放到 Worker 里?因为计算文件 Hash、生成切片、处理图片这些操作都很吃主线程。如果直接在页面里做,用户正在滑动页面,结果主线程被占满,页面一卡一卡的。
Worker 的思路是把这些耗时任务丢到独立线程里执行,通过消息传递结果。以切片上传为例,我可以把“读取文件加计算 MD5 加切片”放在 Worker 里,Worker 跑完把分片列表和 hash 传回主线程,主线程再去发起上传请求。注意:Worker 内部不能直接访问 DOM,它能做的只有计算和数据处理,所以像进度条更新、用户交互这些还得靠消息回传主线程来做。
还有一个天然的 Worker 场景是图片处理。用户上传一张 10MB 的照片,前端先压缩到 2MB 再上传,能大幅减少带宽消耗。压缩代码放在 Worker 里,用 canvas 和 OffscreenCanvas 处理,不卡页面。我做过一个移动端 H5,用户拍完照片上传,如果不用 Worker,压缩过程大概要卡住 1 秒多,用户稍微一划页面就掉帧;换成 Worker 之后,主线程几乎零感知。
3.3 前端传公式、后端算结果的格式设计
热词里还有一条:java 前端传递一个公式,后台根据公式计算。这个场景其实比想象中常见——比如用户在前端配置一个折扣规则 单价 * 数量 * 0.8,后端要根据这条规则来计算最终价格。
第一反应可能是前端传一段字符串,后端用 eval 或者表达式引擎跑。这里有两个坑:安全性和跨端一致性。后端直接 eval 前端传过来的字符串是灾难级的风险,如果模板里混入恶意代码,基本等于注入。所以更安全的做法是:前端把公式解析成 JSON 格式的抽象语法树(AST),传一棵树给后端,后端基于这棵树做安全计算。
举个例子,(price * count) - 100 可以表达成:
json复制{
"op": "-",
"left": {
"op": "*",
"left": {"type": "field", "name": "price"},
"right": {"type": "field", "name": "count"}
},
"right": {"type": "number", "value": 100}
}
前端可以写成自定义公式,实时预览结果,同时把 AST 传给后端;后端只需要遍历这棵树,从数据字典里取出字段值,做加减乘除就行。这样两边各干各的,前端负责编辑和可视化,后端负责安全执行,也不用统一公式语法。
3.4 集成 onlyoffice、bpmn 这类重量级组件的注意点
前端经常要和 onlyoffice、bpmn 这类重量级组件打交道。它们的特点是:功能强,但文件体积大、初始化慢、还有一堆外部资源依赖。
例如 onlyoffice 的集成,官方给的是 doceditor 加载脚本,需要在页面里创建一个 div,并传入配置对象,比如文档类型、语言、自定义工具栏按钮等。首次加载时它会动态加载一大坨 JS 和样式,所以页面最好预留加载位,并加 loading 状态,不然用户以为页面白屏了。
bpmn 流程设计器也一样。bpmn-js 本身是建模工具,集成到项目里时,要注意它的样式是独立打包的,如果项目用了全局 reset,可能会把它的内部布局弄乱。我之前遇到过一次,引入 bpmn 后流程图里的节点位置全部错位,排查半天发现是全局样式把 box-sizing 统一改掉了。解决方法是给 bpmn 容器加一个独立的隔离作用域,或者调整全局样式的影响范围。
4. AI 时代的前端:工具、岗位和自我定位
4.1 AI 编码工具到底能帮你做什么
“AI 出来后前端工程师是不是没了”这个问题几乎每个技术社区都在聊。我的真实感受是:AI 没有让前端消失,它改变的是低端重复工作的生产方式。
像 cursor、GitHub Copilot、通义灵码这些 AI 编码工具,现在写页面模板、写单元测试、写简单的 CRUD 接口确实很顺手。AI 可以根据设计图或者描述直接生成前端代码,比如 Figma 设计图转成 HTML,这在以前要一上午的工作量,现在可能只要 10 分钟。但生成出来的代码,能不能跑通、有没有考虑边界情况、样式是否有兼容问题、代码风格是否统一,还是得靠人来判断。
我用 cursor 的经验是:它擅长快速给出能跑的骨架,但细节还得你来盯。比如让它写一个文件上传组件,它很快能贴出代码,但断点续传、并发控制、错误重试这些边界逻辑,它经常是残缺的。你要是没看明白它写了什么,直接上生产线,迟早出事。
4.2 前端要不要转全栈
“前端转全栈”是这两年热度很高的职业话题。热词里也有 golang gin 集成 vue前端dist 这种场景,说明不少团队的前端已经在碰后端了。
我个人的建议是:转全栈不是为了逃避前端,而是为了补全链路视角。一个前端如果理解 Token 是怎么生成的、接口权限怎么控制、数据库字段怎么设计,跟后端沟通效率会明显提高。比如 Gin 集成 Vue 的 dist 文件,核心其实就是把打包后的静态文件用 StaticFS 挂起来,同时处理好前端路由的 history 模式回退问题。
但是,全栈不等于“两个半吊子”,我见过很多转全栈的人,前端也一般,后端也一般,最后两边都不精。如果你当前前端基本功还没打牢,比如闭包、原型链、事件循环都没搞透,我不建议急着转。先把一门语言和框架吃透,再向后端延伸,这样每一层都有深度,综合能力才真的值钱。
4.3 低代码和 AI 会不会淘汰普通前端
低代码平台这几年发展很快,内部系统用低代码搭,出来得又快又省人力。低代码有没有冲击前端岗位?有。它冲击的是“重复实现后台管理页面”的那部分需求。
但低代码平台本身也得有人开发,那些复杂的、需要深度定制的页面,比如数据大屏、复杂流程编排、性能要求极高的门户页,低代码依然是搞不定的。热门关键词里 avue-data、anything-llm 在 GitHub 上是前端应用,这些其实都是前端的能力延伸——把数据可视化、把 AI 对话界面化,都需要前端来做。我的判断是:低端重复需求会被工具吞掉,复杂交互和体验优化反而会更抢手。
5. 面试高频考点与前端的“八股文”实战
5.1 八股文该怎么准备:理解记忆,而不是死记硬背
前端面试题永远是热点,尤其是每年前端面试题汇总类的内容,流量非常高。这说明面试仍然是前端行业筛选人的主要方式。
所谓八股文,本质是“面试官想确认你有没有系统地学过基础”。比如闭包、原型链、事件循环、防抖节流、深浅拷贝、手写 Promise、数组去重等,这些几乎必考。我的建议是准备的时候不要只背输出顺序、答案列表,而是把原理脉络走一遍。
举一个最常见的例子:事件循环。很多同学能背出“宏任务微任务”几个字,但遇到具体题目还是会错。关键要理清楚:JS 是单线程的,代码执行遇到异步任务会分类排队,微任务在当前同步代码执行完后立即清空,宏任务要等下一个宏任务阶段才执行。你把这条主线理解透了,setTimeout、Promise、async/await 嵌套的题目基本都能答对。
5.2 手写题和场景设计题才是分水岭
面试越来越不满足于“你说你会”,而是要求“你现场写”。手写防抖节流是入门,更高一点的会考手写 Promise.all、实现一个带取消的请求、写一个可拖拽弹窗、设计一个前端监控系统。
场景设计题更值得复盘。比如“让你设计一个前端错误监控系统,你怎么做?”合理的思路是:
- 收集层:监听
window.error和unhandledrejection,收集错误信息、用户行为栈、页面上下文; - 传输层:采用批量上报、失败重试,防止影响正常接口;
- 展示层:聚合错误堆栈,按影响面排序,支持检索和版本归因;
- 报警层:超过阈值自动告警,重要错误及时周知。
这里能看出一个人有没有做过实战项目。同样,热词里 前端js解码h264 这种硬核题目,说明一些团队已经在前端做视频处理了。面对这类题目,最关键的是先表达思路:H264 编码流需要先 demux 成 NALU,再通过 WebCodecs 解码器交给 canvas/WebGL 渲染。你可以不会全部实现,但要让面试官觉得你的知识结构是通的。
5.3 面试前后的几个常见话题速查
这里整理几个我反复被问到、以及我面别人时经常考的问题,可以当作速查表:
| 话题 | 核心考点 | 建议回答方向 |
|---|---|---|
| Vue3 vs React | 响应式原理、组件通信、Diff 算法 | 先从设计思想角度回答,再落到底层实现 |
| 微前端沙箱 | JS 隔离、CSS 隔离、路由协同 | 用 Proxy 加 with 实现 JS 沙箱,样式加作用域 |
| 前端性能优化 | 首屏加载、内存泄漏、长列表 | 先明确性能瓶颈,再给出优化手段,要能带数字 |
| 大文件上传 | 切片、断点续传、并发控制 | 讲清楚分片大小、指纹计算、失败重试的完整链路 |
| Token 与鉴权 | JWT 结构、过期刷新、刷新竞态 | 讲清楚 header、payload、signature 三部分 |
| 前端部署 | Nginx 配置、history 路由回退 | 说清楚 try_files 的作用和缓存策略 |
注意,面试时最忌讳只答一个点。比如问 Vue3 响应式,你答一句 Proxy 和 reflect 就结束了,那等于白送分。要把 reactive、ref、依赖收集、触发更新的完整链路讲出来,面试官才能判断你是真懂还是背了关键词。
6. 联调与部署里的几个真实场景
6.1 token 到底怎么算的,前端该做什么
前后端联调时,token 是绕不开的话题。很多前端其实不理解 token 的后端实现,只知道登录之后拿个字符串存起来,每次请求带上。热词里那条 java后端给前端的token值是如何计算的,说明查的人不少。
简单说,现在主流是 JWT,它是一个带签名的 JSON,分成三部分:Header(声明算法)、Payload(用户信息、过期时间)、Signature(签名)。后端用密钥把前两部分加密算出签名,前端只需要把它当不透明字符串,请求时塞进 Authorization 请求头即可。前端不需要关心 token 怎么签名,但要关注过期刷新逻辑。
刷新有一个经典坑:多个请求同时带着过期 token 发出,结果每个都返回 401,前端就每个都去调刷新接口,刷新接口又被请求对冲。解决方法是做一个“刷新锁”,正在刷新 token 的时候,其他 401 请求先进入队列,等新 token 回来了再统一重放。这个机制在真实项目里非常实用。
6.2 老项目遇上前端新方案:layui、jQuery 与 Vue 的和平共处
热词里有 layui前端框架、java web 加 jsp 项目中前端使用 js 加 jquery 如何实现设置审批流。很多公司还在维护老系统,历史代码是 jsp 加 jquery,新功能想用 Vue3,怎么共存?
我自己的踩坑经验是:新老代码最好不要在同一个页面里混着用。如果一定要混用,必须做命名空间隔离,Vue 实例挂载的容器不要和 jQuery 操作过的 DOM 重叠。否则 Vue 更新视图时,jQuery 绑定的事件和 DOM 状态会被 Vue 覆盖掉,出现“页面数据变了但事件还在旧节点”的诡异 bug。
还有一点是要注意构建链路。老项目如果还在用 script 标签引入 Vue,那就直接引入完整版,别上构建工具那一套,不然要兼容老代码又会引入一堆问题。jQuery 和 Vue 之间靠自定义事件通信,比共享全局变量更稳。
6.3 Gin 集成 Vue dist 部署时常见的路径坑
golang gin 集成 vue前端dist 也是一个高频场景。核心是:Vue 打包后在 dist 目录生成 index.html 和一堆静态资源,Gin 要把这个目录挂载起来,同时处理路由回退。
直接贴个示例思路:
go复制r.Static("/assets", "./dist/assets")
r.StaticFile("/", "./dist/index.html")
r.NoRoute(func(c *gin.Context) {
c.File("./dist/index.html")
})
注意两点。第一,如果前端用了 history 模式路由,比如 /user/list,后端刷新时找不到对应静态文件,会 404,所以 NoRoute 要返回 index.html,由前端路由接管。第二,静态资源缓存策略要配合好:带 hash 的 JS/CSS 文件可以做强缓存,index.html 设置为不缓存,否则发版后用户看到的还是旧页面。我在生产环境里被这个坑过一次,排查了好几个小时,最后发现就是 Nginx 把 index.html 缓存了。
前端这行变化很快,框架和工具像走马灯一样换,但翻来覆去其实还是在解决几件事:让页面更快、让代码更可靠、让团队协作更顺畅。这些随记里的方法,不一定每个都适合你的项目,但里面踩过的坑和排查思路,是我这几年真金白银换来的。如果你也在做前端,建议别光囤资料,挑两三个点在自己项目里动手试一下,试过之后的理解和看文章完全不一样。
