
面试结束后我坐在回去的地铁上脑子里还盘旋着那些题目。说实话投奇安信之前我对安全公司的前端面试没什么概念总以为跟互联网大厂差不多无非是算法、框架、工程化老三样。面完之后我才意识到在安全公司做前端考察的不只是“你写得出来吗”更是“你写的代码会不会成为突破口”。这套“奇安信2020web前端开发工程师二”的题目表面上看是一场常规技术面实际上从JavaScript基础到浏览器原理再到Web安全专项每一环都在筛选一种特定的技术直觉。这篇文章我想把这场面试的完整复盘写下来把每道题背后的考点、面试官想要的答案方向以及我后续整理出的备考路线和踩过的坑都摊开讲清楚适合正在准备安全公司前端岗位、或者想系统检验自己前端基本功的朋友参考。1. 先说结论安全公司的前端面试到底在考什么1.1 安全基因是如何影响前端面试风格的奇安信是干什么的不用我多说国内头部网络安全企业。这种背景直接决定了技术团队的气质务实、警惕、对代码质量有近乎偏执的要求。普通互联网公司前端面试可能更看重业务交付能力和组件化思维但安全公司不一样他们默认你的代码不仅面向用户也面向攻击者。任何一处XSS、任何一次不安全的输入拼接都不只是bug而是漏洞。这场面试给我的整体感受是考察内容覆盖面很广但深度集中在“你能否讲清楚底层原理”这件事上。面试官不满足于你说“这样写可以”他一定会追问“为什么这样写可以”“不这样写会怎么样”“有没有更优解”。比如一道很普通的防抖节流手写题看起来是前端入门必背内容结果他接着问“防抖函数的this应该指向哪里”“事件对象怎么透传”“如果需要在首次立即执行怎么改”完全是在看你能不能把一道题做成一套体系。另外一个明显特点是安全类题目比重不小。这不是那种“你知道跨域就行了”的考法而是真的要求你说清楚XSS的几种类型、怎么从防御者角度去阻断、CSP的指令怎么配置、CSRF为什么SameSiteLax还不够。因为安全公司研发的每一步从设计到上线都要带着威胁建模的思路。前端作为第一道关卡天然要背这个责任。1.2 从“会写前端”到“可信前端”的能力模型我后来复盘发现安全公司前端岗位的能力模型可以拆成三个层级。第一层是基础能力也就是JavaScript、CSS、浏览器原理、HTTP协议、框架机制这些通用前端知识属于所有公司都会考的内容。第二层是工程能力比如构建工具配置、代码规范落地、性能优化、CI/CD协作这是判断你能否在团队里独立扛活的标准。第三层是安全能力包括前端常见漏洞原理与防御、安全编码习惯、对浏览器安全策略的理解这一层是安全公司区别于其他公司的差异点。如果你只是想去互联网公司做业务前端第三层可能只是加分项。但在奇安信这类公司第三层几乎是准入门槛。这不是说你要成为安全专家而是你得有足够的安全敏感度能看懂安全评审时提出的问题能自己写出符合SDL规范的前端代码。2. 逐题复盘我从这场面试里带回来的那些题2.1 JavaScript基础题型闭包、this、事件循环都是必考点第一轮技术面从JavaScript基础开始这部分题目不算刁钻但覆盖面很广而且每个问题都会往下追问。先说一道经典输出题估计大家都在面试里见过for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); }答案当然是5个5。面试官让我解释原因我提到了var声明的函数作用域、setTimeout回调执行时循环早已结束、闭包捕获的是同一个变量引用。然后他让我改成打印0到4我写了两种方案一种是用let形成块级作用域一种是用IIFE传参for (var i 0; i 5; i) { (function(n) { setTimeout(function() { console.log(n); }, 1000); })(i); }原以为这题就过了结果他问“闭包捕获变量的本质是什么为什么IIFE能解决问题”这里要注意面试官要的不仅仅是答案而是把“作用域链”“变量对象”“执行上下文”这些底层概念串起来。我当时从执行上下文的角度解释闭包内部函数持有对外部函数变量对象的引用而每次IIFE执行时都会创建新的执行上下文和新的变量对象所以每次循环的n互不干扰。除了闭包手写Promise相关方法的概率也很高。让我现场实现一个Promise.all这是基本功。我当时写了简易版Promise.myAll function(promises) { return new Promise((resolve, reject) { const results []; let count 0; if (promises.length 0) { resolve([]); return; } promises.forEach((p, i) { Promise.resolve(p).then(value { results[i] value; count; if (count promises.length) { resolve(results); } }, reject); }); }); };写完面试官追问“Promise.all在遇到reject时会立即结束其他还在执行的Promise会怎样结果会被丢弃吗”这个问题有点意思答案是它们仍然会继续执行只是外层Promise已经不会等待它们了。这个追问其实在考察你对异步并发细节的理解不只是背API。2.2 浏览器与性能从URL输入到页面展示漏一个环节都是减分项浏览器工作原理几乎是每家公司的必考题。这套题里它占了不小篇幅核心问题就是在浏览器输入URL到页面展示发生了什么这个问题的完整链路很长我建议的回答框架是DNS解析、TCP连接、TLS握手、HTTP请求发起、服务器处理并响应、浏览器解析HTML、构建DOM树、解析CSS构建CSSOM树、合成渲染树、布局、绘制、合成。每一段都要能展开讲细节比如“在解析HTML时遇到script标签会怎样”就是在考察script是否阻塞DOM解析。这里有个经典的细化追问async和defer的区别是什么我当时的回答是defer会让脚本在HTML解析完成后按顺序执行而async是脚本下载完成后立即执行不保证顺序也不阻塞解析。普通script标签则是在下载和执行时都阻塞解析。面试官补充的一点是defer的执行时机是在DOMContentLoaded事件之前而且多个defer脚本仍然保持文档顺序async脚本则是谁先下载完谁先执行。这个细节容易被忽略但在高并发页面优化中很关键。性能优化部分他问的是“如果一个首屏页面打开很慢你怎么排查和优化”。这个问题不能只答“图片懒加载、路由懒加载”这种碎片方案更好的回答是分层结构。我给出的思路是先确认性能瓶颈在哪个阶段用Performance面板看白屏时间、首屏时间、可交互时间再针对网络请求数、资源体积、渲染阻塞脚本去做具体优化。我提到了三个方向加载阶段优化减少请求数、合并压缩静态资源、设置合理的缓存策略、开启HTTP/2渲染阶段优化减少重排重绘、降低CSS嵌套层级、使用RAF节制动效、内容分层合成交互阶段优化交互动画采用transform和opacity避免操作引起布局抖动。面试官又追问了重排和重绘的区别以及触发条件我列举了改宽度高度、增删DOM、读取offsetWidth等会导致重排的操作而改颜色、背景图只触发重绘。能举出具体API的例子比笼统说“影响性能”更有说服力。2.3 Vue源码与工程化响应式原理和diff算法不能只背结论这套题目在框架层面锁定的是Vue2这也符合2020年前后的主流技术栈。最核心的问题Vue2的响应式原理是什么答案是Object.defineProperty对data中的每个属性进行getter/setter拦截每个组件实例在初始化时会对data进行递归遍历把属性都转为访问器属性。当访问属性时依赖被收集到当前Watcher中当属性被修改时setter被触发通知依赖更新于是视图重新渲染。但这个答案只是骨架面试官一定会往细节上追问。他问我“Object.defineProperty有哪些缺陷”我列了三点无法监听新增属性和删除属性所以官方提供了Vue.set和Vue.delete来补充对数组的索引修改无法生效官方通过重写数组的push、pop、shift等方法实现劫持对象层级较深时递归遍历的初始化成本比较高。再往下还问了虚拟DOM和diff算法。这部分我的表达方式是先用一句话定位价值虚拟DOM让框架实现了“用JavaScript计算差异再最小化更新真实DOM”的思路避免频繁操作真实DOM导致的性能问题。然后说明diff的核心逻辑是“同层比较、双指针策略”优先对旧头新头、旧尾新尾、旧头新尾、旧尾新头进行四种比较再处理key不匹配时的查找逻辑。为什么需要key因为key能帮助diff算法精确识别哪些节点是复用的哪些是新建的这直接决定了列表渲染的性能。工程化部分围绕Webpack展开。他问了webpack构建流程和loader与plugin的区别。我简要说过webpack从entry开始递归解析依赖生成模块经过loader转换再经过plugin介入优化最终输出bundle。loader是文件转换器改变具体文件的内容plugin是事件钩子在构建生命周期里做额外的事情比如打包前清理目录、压缩代码、注入环境变量。还问了tree shaking的原理这个我之前写过一篇文章所以答得比较顺tree shaking建立在ESModule静态结构之上借助sideEffects和usedExports来标记并删除未被引用的模块代码。2.4 安全专项XSS、CSRF、CSP、路径校验这场面试的重头戏这部分是奇安信这场面试最有辨识度的地方。普通前端面试讲跨域讲认证就够了但这里直接往漏洞利用与防御方向深挖。第一个问题解释XSS的三种类型以及如何防御。我按存储型、反射型、DOM型来拆。存储型是恶意脚本被保存在服务器中每次用户请求时都会被加载反射型是脚本作为参数拼接到HTML中返回DOM型则是纯粹在前端完成由浏览器解析用户输入时执行了非法脚本。防御手段我概括成三层输入侧做校验与过滤、输出侧做编码转义、传输侧设置CSP限制脚本来源。还要配合HttpOnly保护Cookie内容不被脚本读取能设置Cookie的同时设置Secure和SameSite属性。他追问了一个很现实的问题“如果业务要求前端必须动态注入一段HTML你怎么办”我给出的是白名单策略使用DOMPurify等库清洗HTML标签和属性只放行白名单内的标签并且所有事件属性一律过滤。不能用简单的正则黑名单因为攻击载荷变体太多黑名单永远有绕过的风险。第二个问题是CSRF的防御方案。我从原理说起用户登录网站A浏览器保存了Cookie用户又访问了恶意网站BB发送跨站请求到A浏览器自动带上Cookie导致A以为是用户本人操作。防御方案有校验请求头中的Origin和Referer使用CSRF Token服务端生成随机Token并在请求中校验关键的Cookie设置SameSite属性限制跨站请求携带部分敏感操作做二次认证。面试官提示我“还有一点容易忽略”我想了一会才补充对JSON接口也要防范CSRF因为早期很多CSRF防护只验证表单请求同时GET请求必须保持完全幂等所有变更操作必须用POST或其它合适的方法避免用URL直接触发。第三个问题很有安全公司特色文件上传与路径遍历。他问我“前端上传文件名中包含../../会遇到什么风险”。这个问题的答案在服务端但前端作为第一道关口也需要理解。如果服务端没有对上传文件名做规范化攻击者可能通过路径穿越把文件写到服务器任意目录再结合解析漏洞拿到Webshell。前端至少要做两件事统一重命名文件不信任用户原始文件名对上传内容做类型校验和大小限制。更健壮的做法是在服务端把文件名映射为随机字符串并校验文件真实的MIME类型而不是只看扩展名。最后还聊到了Content-Security-Policy。我给出了一个基础配置示例Content-Security-Policy: default-src self; script-src self https://trusted.cdn.com; style-src self unsafe-inline; img-src * data:; connect-src self /api从这张表可以看出前端团队要想把CSP落地就必须梳理清楚资源的加载来源不能直接全开。如果业务太杂、外部脚本太多可以先开启报告模式观察违规数据再逐步收紧策略。奇安信的面试官对CSP非常熟悉他甚至直接指出我示例里容易踩的坑如果某个版本浏览器的某个指令兼容性有问题CSP可能导致线上脚本全部失效。所以生产环境要用真实项目做灰度验证不能写个配置就切过去。2.5 手写题现场防抖节流、深拷贝、数组去重考察的不只是能写而是怎么写好轮到现场手写环节题目其实都是前端圈里耳熟能详的防抖节流、深拷贝、数组去重。但面试官在现场加了很多限制。防抖题他要求支持立即执行选项于是我的实现是function debounce(fn, wait, immediate) { let timer null; return function(...args) { const context this; const callNow immediate !timer; if (timer) clearTimeout(timer); timer setTimeout(() { timer null; if (!immediate) fn.apply(context, args); }, wait); if (callNow) fn.apply(context, args); }; }写完他点评这个版本的问题在于连续触发时timer被不断清除如果中间有一次触发且callNow为true就会在第一次立即执行但之后每次都只在wait结束时执行一次逻辑上没问题但要注意调用时传参的时机。这个题目背后考察的是对this和arguments的理解不能只背模板。数组去重要求“在原数组上原地去重不额外创建大对象数据量很大怎么办”。我就讨论了Set去重、双循环去重、排序后相邻去重以及布隆过滤器思路的取舍。深拷贝是必考题我写的是考虑数组和对象、循环引用、Symbol键的递归实现。用WeakMap缓存已拷贝的引用并用Object.getOwnPropertySymbols处理Symbol属性。面试官问为什么不直接用JSON.parse(JSON.stringify())我列了一堆限制无法处理函数、undefined、Symbol、循环引用Date会变成字符串RegExp变成空对象。3. 事后复盘前端安全求职的备考路线我是怎么整理的3.1 别刷偏门题先把高频手写题焊死在肌肉记忆里面试结束之后我最大的感受是手写题不是靠面试现场发挥的。面试现场本身有环境压力再加上面试官随时追问如果你对题目不够熟悉很容易写一半思路断了。尤其是防抖节流、深拷贝、Promise.all这些题熟到一定程度是可以“无脑”写出来的只有这样你才有余力去考虑面试官追加的场景变化。我自己整理了一份优先级清单按面试出现频率和重要程度分了三个梯队。第一梯队防抖节流、深拷贝、Promise系列all、race、allSettled、数组去重、数组扁平化、call/apply/bind、new实现。这些属于必会中的必会几乎每场前端面试都会碰到。第二梯队instanceof实现、EventEmitter实现、柯里化、手写订阅发布、排序算法快排、归并。第三梯队虚拟DOM渲染、模板字符串解析、CSS布局题这类题偶尔出现但难度较大。3.2 用“讲解式学习”代替“背答案”把知识变成表达我在复盘时发现一个很重要的转折很多知识点我原本以为自己是懂的比如虚拟DOM、响应式原理、事件循环但面试官让我讲的时候我讲得很干涩。问题在于我一直是“背答案”的状态而不是真正理解。后来我换成一种“讲解式学习”的方式。方法是找一个空白文档假装面对一个刚入行的人把某个知识点从头到尾讲一遍。这个过程会逼你把逻辑理顺把术语转换成口语把模糊的地方暴露出来。讲不通的地方就是你还没理解透的地方。比对着面经背十遍都管用。比如事件循环我会这样讲JavaScript是单线程的但浏览器通过任务队列机制来处理异步。同步任务先执行遇到异步任务就先挂起等到时机到了把回调放进任务队列。当前宏任务执行完会清空微任务队列再进入下一次事件循环。Promise.then推入微任务setTimeout推入宏任务。这个逻辑本身不难但你真的能脱口而出“微任务执行时机是当前宏任务结束、下一个宏任务开始之前”的时候面试状态完全不一样。3.3 模拟面试必须有人追问不能只对镜练习我前期准备时犯了一个错误一直自己模拟自己提问回答都是“计算内存中的理想状态”从没被追问卡过。一到真实面试被人家紧跟着问“为什么”“那如果不这样做呢”“换个场景怎么办”我就露馅了。后来我拉了一个同做前端的朋友做了三轮模拟面试规则是对方必须打断我必须在我答完一段后追加至少三个追问追问方向完全随机。第一次模拟面试我就被问蒙了比如我答到Webpack的loader时他追问“如果loader内部需要拿到资源配置信息有没有更合适的工具”我当时愣住了。但正是这种尴尬帮我在正式面试前补上了很多知识盲区。如果你身边没有合适的人可以用一个变通办法把面试题写在一张张卡片上随机抽题自己录音回答然后回放录音找问题。重点是“随机抽题”和“刻意追问”这两个动作不能让自己沉浸在舒适区里。4. 面试失误实录那些我没答好的瞬间和后续改进4.1 我在跨域问题上翻车了CORS、JSONP、代理混成了一锅粥面试中有一道跨域题线上接口与前端页面不同域怎么解决我一开始答得还行说了JSONP、CORS、nginx反向代理三种方案。但面试官追问“CORS的预检请求什么时候触发、options请求的响应头有哪些”我说得磕磕绊绊尤其把CORS和代理的适用场景说混了。现在复盘清晰思路应该是这样JSONP只支持GET请求本质是利用script标签不受同源策略限制的特性动态加载资源CORS通过服务端返回Access-Control-Allow-Origin等响应头来实现跨域支持各种HTTP方法预检请求发生在发起真实请求之前触发条件是请求方法和请求头不满足简单请求条件nginx反向代理则是让浏览器同源请求代理服务器由代理服务器转发到目标服务端从浏览器角度看所有请求都是同域的。这三种方案的适用场景完全不同面试官追问的意图是看你能不能根据业务场景做取舍。比如如果目标接口不允许服务端改动那就只能走代理如果接口是自己团队维护的CORS往往是最简单的方案。4.2 答性能优化时只列点没有建立“监控-量化-优化”的闭环性能优化那道题我一开始回答得偏库式罗列图片压缩、代码分割、缓存、CDN……面试官倒是没打断我但我讲完后他问了一句“你怎么知道优化前后效果好不好”这是一个很好的提醒。正确思路应该是从现有数据出发先通过PerformanceAPI、Lighthouse、埋点拿到FCP、LCP、TTI等指标再设定优化目标。优化之后要回到同一套指标下做对比验证方案有效性。监控要长期保留。我后来整理了一套方案用PerformanceObserver采集关键指标上报到日志平台定期汇总分析性能优化才能持续迭代。没有数据支撑的优化方案听上去再全面也只是“感觉优化”。4.3 职业规划回答太空缺乏可执行路径非技术类问题同样值得关注。面试官问我“未来三年计划”我当时的回答是“想在前端领域深耕提升工程能力和业务能力”。这话没错但太虚了。比较好的回答方式是给出具体路径和判断标准。比如我后来重新准备时是这样说的第一年目标是独立负责核心业务的前端架构包括组件库建设、性能监控体系搭建、安全编码规范落地第二年希望深入参与前端安全相关的工作比如对存量代码做XSS隐患排查、建设前端漏洞扫描工具链第三年希望能带一到两个人把团队的安全编码意识规范化。这样的回答有目标、有节奏、也有可验证的产出。5. 如果重来一次我会怎么准备安全公司的前端岗位5.1 把安全思维嵌入日常开发而不是临时背题安全公司面试最怕的就是你“临时背安全题”。真正的安全思维不需要背它体现在你写代码的习惯里。我后来把这几点列成了自己的开发规范插值优先使用文本绑定而不是HTML绑定确实需要HTML时走白名单清洗所有给用户看到的输出不管来自用户输入还是数据库都默认不可信Cookie设置默认带上HttpOnly和SameSiteLax只有明确必要性才放开接口请求统一走封装层统一处理错误信息避免把堆栈详情直接抛给用户前端路由和权限控制只是体验层面的控制真正的权限校验必须由服务端完成。你把这些习惯写进自己的代码里面试时讲到安全题目就不需要刻意表演因为你本来就会这样写。5.2 准备一个“安全向作品集”用实际产出说话在面试中我因为提到自己做过一个前端安全检查的小工具面试官明显更感兴趣了。这个工具的原理其实很简单扫描项目代码中可能出现的innerHTML、document.write、eval等危险调用在CR阶段输出提醒。市面上有现成的ESLint安全插件比如eslint-plugin-security但自己写过一遍你对哪些地方容易出漏洞会更有体感。类似的可以做的方向还有给团队搭建一个CSP配置生成器输入业务资源域名清单输出一套可发布的CSP策略做一个登录页的密码强度实时校验组件写一篇关于前端日志脱敏的实践文章。这些都是成本可控、又能体现安全思想的小项目。5.3 长期方向让安全成为你区别于其他前端的竞争壁垒我个人的体会是安全公司前端岗位其实是在“前端工程师”和“应用安全工程师”之间搭建了一座桥。你不需要成为掌握二进制漏洞挖掘的安全研究员但你完全可以成为团队里最懂Web安全的前端。这个定位的价值在于它能让你在业务评审会上提前发现潜在风险在接口设计阶段提出更合理的鉴权方案在代码Review时发现别人忽视的漏洞点。这种能力很难一蹴而就但积累了几个月之后你会发现自己的代码风格会变得非常克制而这份克制恰恰是安全公司最需要的东西。这场面试让我最受益的并不是某一个题目本身而是它逼着我把那些常用但没细究的知识点重新从底层捋了一遍。如果你也在准备类似的面试建议你按照上面几部分内容去做对照检查基础是不是都理解到位了安全题是不是能讲出防御逻辑手写题是不是能扛住追问。技术面试没有捷径但有了正确的复盘方法至少能少走很多弯路。