前阵子刚面完一个前端工程师的社招岗位趁着记忆还热乎把整场面试里涉及的知识点、我的回答思路、答得不好的地方以及事后复盘下来的改进方案全部梳理了一遍。这篇文章我先把公司信息隐掉只保留岗位本身和考核内容因为面试里真正有价值的是那些反复出现的技术问题而不是公司名字。先说结论社招前端面试和校招完全是两种逻辑。校招看基础扎不扎实社招看你能不能解决实际问题、能不能带项目往前跑、遇到线上故障慌不慌。整个面试过程大概四轮前三轮都是技术面最后一轮是负责人面。每一轮考察的侧重点都不一样但有一条主线贯穿始终你的项目是不是你自己写的你说出来的性能优化是不是真的实践过你对框架原理的理解停留在“用过API”还是“读过源码”。这篇文章适合两类人正在准备社招的前端工程师以及想从业务开发往更资深方向走但不知道从哪补起的同学。我会把每一轮的考察点、我踩过的坑、以及事后总结出的答题框架都写清楚内容偏向中后台业务前端但也覆盖大部分通用场景。1. 岗位画像与面试流程拆解1.1 社招和校招的差别校招面试官默认你是白纸主要验证你有没有潜力所以会问“原型链是什么”“闭包会不会造成内存泄漏”这类基础题。社招面试官默认你已经是能干活的人他们更关心你过去做的项目规模、你在项目里扮演的角色、上线后遇到什么问题、你怎么排查和解决。这种差别直接决定了准备方向。我这次面试前没有再去背一遍红宝书而是把重点放在了两块一是把最近两年做过的最有分量的项目重新捋了一遍每一个技术决策都问自己“为什么这么做”二是把框架的源码链路重新梳理了一遍从“用法”上升到“设计”。基础八股当然也看但只是作为查漏补缺不再花大量时间死记硬背。1.2 四个环节分别在考什么第一轮通常是同事面考察基础是否扎实JS语言特性、CSS布局、网络协议、浏览器原理。这一轮的目的是确认你不是“会用框架的切图仔”而是对web底层有真实理解的人。第二轮是技术负责人面重点考察项目深度和工程化能力。面试官会深挖你做过的项目问性能优化具体怎么做的、指标提升了多少、代码质量怎么保证、有没有做过公共组件库或工具链。会当场给一个场景题比如“如果你来设计一个前端监控系统你会怎么做”考察的是系统设计能力和边界思考。第三轮是交叉面或总监面更偏综合能力和业务思维。会问你在团队里的角色、跨部门协作的冲突怎么处理、怎么推动一件事落地。这个时候技术已经不是唯一重点表达逻辑和做事方法更重要。第四轮是HR面聊薪资、离职原因、团队氛围。这个环节看似轻松但同样有陷阱。关于离职原因千万不要吐槽前公司关于期望薪资要提前做好市场调研报一个合理的范围。1.3 面试官手里的那张评分表我自己也做过面试官所以我知道大多数公司都有一套标准化的评分表大概包括几个维度考察维度具体内容权重基础知识JS/CSS/网络/浏览器原理25%框架能力原理理解、源码阅读、生态熟练度20%工程化能力打包配置、CI/CD、代码规范、监控15%项目深度项目复杂度、角色贡献、技术决策25%软素质沟通表达、逻辑思维、学习能力15%这个表给我的启发是不要只准备技术题项目经验至少要占四分之一以上的精力。很多人挂在第二轮不是基础题答不上来而是项目讲得像流水账面试官听完根本不觉得这项目有难度。2. 技术准备JS 基础和框架原理2.1 闭包、原型链、事件循环高频考点怎么答第一轮技术面问了三个高频题闭包是什么、原型链继承的几种方式、事件循环的执行顺序。这三道题我基本能答上来但复盘时发现回答深度还不够。比如闭包只回答“函数里返回一个函数内部函数能访问外部作用域变量”只能算60分。更好的回答是分三层第一层讲语法第二层讲作用域链机制第三层讲实际应用和坑。实际应用包括防抖节流、柯里化、react fiber里闭包的使用坑包括循环里用var声明变量导致的经典问题、闭包导致内存泄漏的场景。我重新整理了一个作答模板闭包发生的根本原因是JS采用词法作用域函数在定义时就锁定了它的作用域链。内部函数引用了外部函数的变量导致外部函数执行结束时这个变量仍然被内部函数持有不会立即被回收。以闭包实现计数器为例我们可以这样理解每一次调用createCounter返回的函数都独立持有了一个count变量互不干扰。这么答的好处是既展示了底层原理又展示了你真的在业务里用过。事件循环那道题面试官给了这样一段代码console.log(script start); setTimeout(() { console.log(timer); }, 0); Promise.resolve().then(() { console.log(promise1); }).then(() { console.log(promise2); }); console.log(script end);第一轮问输出顺序第二轮追问“如果setTimeout是5秒后执行Promise里的任务还会阻塞吗”。这道题的关键是理解宏任务和微任务的执行时机。正确输出是script start、script end、promise1、promise2、timer。setTimeout即便延时为0也要等当前同步代码执行完再清空微任务队列然后才轮到宏任务队列。这里最容易答错的是“Promise优先级高于setTimeout”这个结论不能只背结论要理解为什么。2.2 框架面试题从使用到原理框架题我现在倾向于答到“设计思想”这一层。拿Vue和React举例面试官问“为什么数据变了页面自动更新”如果你只回答“因为Vue用Object.defineProperty实现了响应式”是不够的还要说清楚依赖收集的过程每个组件渲染时会访问响应式数据触发getter此时建立“数据-组件”的对应关系。派发更新的过程数据变化触发setter通知依赖的组件重新渲染。为什么Vue3改用Proxy因为Proxy可以监听对象新增属性、删除属性和数组索引变化而Object.defineProperty对新增属性无能为力。React这边常问“diff算法的时间复杂度为什么能做到O(n)”。关键是那两个假设不同类型的元素直接替换通过key可以唯一标识列表中的元素。这两条假设让React不需要做全量树对比只需同层比较。如果你能进一步提到fiber架构解决了递归渲染导致主线程长时间占用问题把渲染拆成可中断的单元就比单纯背diff算法高级很多。这次面试还遇到一个实际问题“用v-for渲染列表为什么key不建议用index”。我答的核心是key的作用是让diff算法识别节点是否复用了用index当key当列表中间插入一项时后面所有节点的key都会变React/Vue会认为所有节点都不是原来的节点导致不必要的重新渲染。比如一个表格每一行有本地状态输入框内容中间插入一行后面的输入框会被重建内容丢失。框架原理这块有一个必须避开的坑不要硬背源码而是带着问题读。我这次面试前挑了几个关键路径读了一遍比如Vue3的初始化流程、React的render流程但没有逐行看。面试官追问到“你觉得这个设计有没有缺点”时我才能基于理解回答而不是复述源码。2.3 手写题练习清单三轮技术面里穿插了手写题几乎都是经典题提前练过一轮就没问题。这次被要求手写的有防抖函数、深拷贝、Promise.all还有一个数组扁平化。我把对应的实现思路记在这里。防抖的思路是每次事件触发时如果之前有定时器就清掉重新开一个定时器保证只有最后一次触发才会执行。function debounce(fn, delay 300) { let timer null; return function (...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; }这里有个细节很多人会忽略fn.apply(this, args)里的this指向。如果使用箭头函数写定时器this会继承外层词法作用域此时不需要apply。但我选择了普通函数所以要把this传进去。手写题不仅考结果还考你对边界条件的把握。深拷贝要处理的边界包括数组、对象、Date、RegExp、Map、Set、循环引用。如果只写一个递归复制普通对象遇到循环引用会栈溢出。我给出的版本如下function deepClone(target, map new WeakMap()) { if (typeof target ! object || target null) return target; if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target); if (map.has(target)) return map.get(target); const cloneTarget Array.isArray(target) ? [] : {}; map.set(target, cloneTarget); for (const key in target) { if (Object.prototype.hasOwnProperty.call(target, key)) { cloneTarget[key] deepClone(target[key], map); } } return cloneTarget; }用WeakMap存储已经克隆过的对象好处是不会阻止垃圾回收比用普通Map更合适。面试官当时追问“为什么不用Map”我答了WeakMap的键是弱引用当原始对象不再被引用时WeakMap中的键值对可以被回收这样深拷贝不会造成额外内存泄漏。Promise.all的手写题考察的是对Promise静态方法的理解。核心思路是遍历传入的Promise用then收集结果同时维护一个计数器当计数器等于数组长度时resolve一个结果数组。还要注意处理空数组和异常拒绝的情况。function myPromiseAll(promises) { return new Promise((resolve, reject) { const results []; let count 0; if (!promises.length) { resolve(results); return; } promises.forEach((promise, index) { Promise.resolve(promise).then((value) { results[index] value; count 1; if (count promises.length) resolve(results); }).catch((err) { reject(err); }); }); }); }当时写这个的时候有一个bug我没有用Promise.resolve(promise)包一层如果传入的数组里混有普通值比如[1, Promise.resolve(2)]直接调用promise.then就会报错。面试官提示了一下我才意识到。这个小细节很值得记录。3. 工程化与性能优化社招的加分项3.1 打包工具的考察点第二轮面试有一道题是“webpack的构建流程”。我没有直接背loader、plugin的区别而是从入口文件开始讲起串联整个流程把入口文件解析成AST找出依赖关系形成一个依赖图然后递归处理每个模块使用对应的loader将文件转换成JS能理解的模块最后根据依赖图生成bundle并在运行时通过__webpack_require__加载各个模块。面试官接着问“webpack和vite有什么区别”。我给的框架是三点开发模式不同、构建策略不同、工程思维不同。webpack在启动时分析整个依赖图然后打包成一个或多个bundlevite利用浏览器原生ESModule的能力开发时按需加载启动速度更快生产构建时vite底层用的是Rollup。vite最大的优势其实不是快而是让开发服务器和构建产物分离esbuild负责依赖预构建Rollup负责生产打包各取所长。这个时候面试官追问了“为什么webpack启动慢却没有被vite完全替代”。我答的是webpack生态成熟兼容性稳定大型项目插件体系完善很多旧项目迁移成本高。vite对node版本和浏览器要求都比较高在一些需要兼容老旧浏览器或复杂loader场景下webpack仍然是稳妥的选择。这个回答既没有捧一踩一又显示了自己做过对比权衡。3.2 性能优化从哪些维度回答性能优化是社招前端必问的一题不能只背“图片懒加载、代码分割、CDN加速”这种泛泛的答案。我这次给自己定了一个回答框架按“输入、处理、输出”三个阶段拆。输入阶段优化的是资源加载。从减少请求数、优化资源体积、利用缓存三个方向回答。具体手段包括HTTP缓存策略、chunk分包、gzip或br压缩、图片格式转换avif/webp、字体按需加载、CDN边缘节点等。处理阶段优化的是代码执行。包括减少重排重绘、优化长任务、使用Web Worker处理计算密集任务、把大型组件拆成异步加载、避免主线程阻塞。如果业务里有图表库可以提到按需引入如果有大数据列表可以提到虚拟滚动。输出阶段优化的是渲染链路。首屏优化最直接的手段是SSR或静态化其次是按路由拆chunk降低首屏需要加载的JS体积。然后面试官问了一个让我印象很深的问题“你怎么确认你的优化有效”。很多人的回答是“页面变快了”这种主观感觉毫无说服力。我给出的答案是通过Performance API和performance panel录制页面加载过程记录FP、FCP、LCP、TTI、CLS这些指标同时用构建工具的体积分析插件对比优化前后的bundle体积如果做了接口优化对比网络面板里的耗时上线后通过监控平台关注P75/P90的变化曲线。没有数据支撑的优化就是刷流氓这也是面试官真正想听的。3.3 监控与稳定性建设这一块是很多前端工程师的盲区但社招面试越来越爱问。这次的场景题是“如果让你设计一个前端错误监控系统你会怎么设计”。我的回答分四层采集层通过window.addEventListener(error)捕获运行时错误通过window.addEventListener(unhandledrejection)捕获未处理的Promise异常对资源加载错误使用捕获阶段接入error事件。同时通过重写xhr和fetch来收集接口错误信息。上报层将错误信息打包成结构化数据包括错误堆栈、发生时间、页面路径、用户操作行为、浏览器UA、设备信息。上报形式可以是Fetch sendBeacon避免页面卸载时请求被取消。聚合层服务端对错误堆栈做归一化去掉动态内容比如数字、时间戳只保留代码路径计算相同错误出现次数按hash聚类。这样每天几千条错误才能快速找到最关键的那几个。告警层根据错误聚类结果设置告警规则比如某个错误产生速度超过阈值或影响用户数大于多少就推送告警到工作群。再配合上报的错误堆栈可以定位到源码地图还原的具体位置。这个回答本身并不难但是需要你真的动手做过否则只是背概念。我当时因为给项目搭过一套轻量监控所以能讲出具体字段设计和采样策略。面试官在追问“如果需要监控用户点击行为怎么做”时我补充了全局事件代理在document捕获阶段插入一个标记类似>