2019年春天,我拿到了奇安信的前端开发笔试题,当时的第一反应是:这家公司考的怎么这么“硬核”。别的公司还在问Vue生命周期和flex布局,它已经把手写深拷贝、事件循环输出题、XSS防御方案、甚至webpack的plugin机制全部塞进了一张卷子里。做完之后我复盘了很久,后来这些年也陆续帮朋友看过不少大厂前端笔试题,回过头再翻开这份试卷,很多东西依然有参考价值。
这篇文章我会以“过来人”的视角,把奇安信2019春招前端开发试题里最典型的几类考点拆开讲清楚:为什么考、怎么答才拿分、有什么容易踩的坑。无论你是准备面试的候选人,还是负责招聘出题的同行,抑或是想夯实基础的中级前端,这篇内容应该都能给你一些启发。
1. 试题整体设计与考察思路
1.1 从题型分布看考察方向
奇安信2019年春招前端试题的题型,整体可以划分为四类:选择题与填空题、手写代码题、简答题、综合场景设计题。其中选择题覆盖很广,从HTML语义化标签到TCP三次握手都有涉及,但难度不算深,主要用来筛掉基础不牢的人。
真正拉开差距的是手写代码题和简答题。那年的笔试题里,手写防抖节流几乎是必考项,深拷贝也出现了。这类题不是让你默写,而是看你面对边界条件时能不能想到、能不能处理干净。我印象里有一道题是问“深拷贝时遇到循环引用会怎样”,很多同学答“会栈溢出”,但只有少数人能写出用WeakMap解决循环引用的方案。这种细节就是阅卷时的分水岭。
1.2 为什么奇安信的题比一般公司“硬”
奇安信不是普通互联网公司,它的核心业务是政企安全、数据安全、安全运营平台。这意味着两件事:第一,前端工程师要做大量复杂的管理后台、可视化大屏、数据报表,所以对JavaScript性能和渲染性能有要求;第二,作为安全公司,它对Web安全的理解必须刻进每个岗位的骨子里,包括前端。
这一点直接反映在试题里:比如有一道简答题是关于XSS的防御方案,还出现了“输入校验与路径遍历”这类带有安全色彩的考点。普通公司可能把Web安全放在加分题里,奇安信直接把它放在必答题里。我当时做完就有一种感觉:这不是单纯招前端,而是在招“懂安全的前端”。所以如果你准备投这类安全公司,web安全基础、前端安全编码习惯一定不能裸考。
2. 核心考点拆解:JavaScript基础与进阶
2.1 原型链与this指向问题
原型链和this指向,在2019年的前端面试里属于“保留曲目”,奇安信也不例外。它出现在一道选择题里,给出一段代码,让选出输出结果,代码大概长这样:
function Foo() { this.value = 1; } Foo.prototype.getValue = function() { return this.value; }; const foo = new Foo(); const getValue = foo.getValue; console.log(getValue());这题的核心考点有两个:第一,this的绑定规则——getValue是作为普通函数调用的,此时this指向window(或undefined,取决于是否为严格模式),而不是foo;第二,Foo.prototype上的方法,本质上就是普通函数,它的this不天然绑定到实例上。
答题时建议按“四步走”思路:先判断调用方式,再套绑定规则,然后考虑严格模式影响,最后确认输出。很多人在这题上栽跟头,是因为把“方法定义在原型上”和“方法调用时绑定this”两件事混淆了。我自己的经验是,做这种题不要背结论,要练习用规则推导。
2.2 事件循环与异步编程顺序
异步编程的考察,主要是让你预测一组setTimeout、Promise、async/await的输出顺序。比如:
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); async function test() { console.log(4); await Promise.resolve(); console.log(5); } test(); console.log(6);大部分人会答成“1、4、6、2、3、5”,而正确顺序是“1、4、6、3、5、2”。这里的关键在于:await后面跟着的Promise.resolve()会生成微任务,但await本身会立即让出执行权,所以后面的console.log(5)是等微任务执行完才运行的,而微任务又优先于宏任务。
这个考点考察的其实不是背结论,而是对事件循环机制的理解。如果要展开讲,宏任务和微任务的执行规则可以画成一句话:每执行完一个宏任务,都会清空整个微任务队列,再进入下一个宏任务。答题时如果能顺带提一句Node.js和浏览器在事件循环实现上的差异,那会是一个不错的加分点。
2.3 手写代码题:防抖、节流、深拷贝、Promise.all
手写题是奇安信笔试的“重头戏”。当年比较常见的是防抖和节流,现在几乎已经是前端面试的标配了,但放在2019年,能写得干净利落的人比例并不高。防抖的核心是设置一个定时器,在连续触发时重置时间,保证最后一次触发后等待一段时间才执行:
function debounce(fn, delay = 300) { let timer = null; return function (...args) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; }节流的常见实现是时间戳版本或定时器版本。我在答题时一般写定时器版本,因为它更好理解:
function throttle(fn, interval = 300) { let timer = null; return function (...args) { if (timer) return; timer = setTimeout(() => { fn.apply(this, args); timer = null; }, interval); }; }这里容易忽略的点有两个:一是this的透传,用了apply才能让原函数在正确的上下文里执行;二是防抖和节流的使用场景,面试官很可能追问“什么时候用防抖,什么时候用节流”,我的习惯是答“输入框实时搜索、按钮重复提交用防抖,滚动监听、拖拽、resize用节流”。
深拷贝这个题也是必背内容,但真正高分答案要处理这些边界:Date、RegExp、Symbol、函数、循环引用。
function deepClone(obj, hash = new WeakMap()) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags); if (hash.has(obj)) return hash.get(obj); const clone = Array.isArray(obj) ? [] : {}; hash.set(obj, clone); for (const key of Object.keys(obj)) { clone[key] = deepClone(obj[key], hash); } return clone; }注意这里用了WeakMap而不是Map,因为WeakMap的键是弱引用,不会造成内存泄漏。循环引用是关键场景:var a = {}; a.self = a;如果不做缓存处理,递归会无限循环导致栈溢出。这个边界条件,2019年的考卷上如果写出来了,基本就是满分思路。
3. 浏览器端必考:渲染机制与性能优化
3.1 从输入URL到页面展示的完整链路
奇安信的前端考题里有一类“串链路”题,让你描述从输入URL到页面展示的完整过程。这种题考察的是知识树的完整性:DNS解析、HTTP请求建立、服务器响应、浏览器解析渲染,每一步都可以深挖。
我当时回答时把过程分成四段:DNS解析域名拿到IP;通过TCP三次握手建立连接,如果是HTTPS还要加上TLS握手协商密钥;浏览器发起HTTP请求,服务器返回HTML、CSS、JavaScript;浏览器解析HTML构建DOM树,解析CSS构建CSSOM树,两者合成渲染树,经过布局和绘制后呈现在页面上。如果页面里有图片、JavaScript,还要考虑并发加载和阻塞解析的问题。
答题时有个加分细节:渲染过程中,<script>标签会阻塞DOM解析,所以需要把脚本放在</body>前,或者加defer/async属性。这个点看似基础,但结合后面的性能优化题来回答,就能让阅卷人看出你理解的是“为什么”,而不是“背答案”。
3.2 回流、重绘与合成
奇安信考题中关于性能的题目,很多会落到“回流(reflow)与重绘(repaint)”上。比如有一道题问:为什么用transform做动画比修改top/left性能好?答案不只是“transform不会触发回流”,更准确的说法是:transform会触发合成层,动画过程在合成线程完成,不涉及主线程的布局和绘制。
答题时我建议把浏览器渲染管线列出来:DOM树构建 → 样式计算(Style) → 布局(Layout) → 绘制(Paint) → 合成(Composite)。如果修改left,会触发 Layout 和 Paint,后续每一步都要重新执行;如果修改transform,只是改变合成层的位置,Paint 和 Layout 都能跳过,所以更流畅。
为了体现深度,可以补一句:减少回流还可以通过批量修改样式、使用documentFragment或先把元素设为display: none再进行DOM操作。这些技巧本身就是性能优化题的答案素材。
3.3 线上数据大屏的性能优化切入点
结合奇安信的业务场景,数据可视化大屏很可能是日常开发中绕不开的模块。我在实际做这类项目时有一个体会:大量图表同时渲染时,性能瓶颈往往不在绘图本身,而在数据的处理和DOM的更新方式。
常用的优化策略包括:图表按需渲染而不是一次性渲染全部;把图表实例的setOption操作合并,减少不必要的重绘;利用requestAnimationFrame控制更新频率;大数据量时用canvas而不是svg。答题时如果能把性能和业务场景结合起来,比如“安全运营中心的告警大屏,如果有上万条告警同时滚动更新,应该怎么做优化”,那就能体现出你在真实项目中思考过,而不是只会背概念。
4. 前端安全方向:安全公司的独特考察点
4.1 XSS的类型与防御措施
奇安信作为安全公司,前端试题里出现XSS太正常了。这类题通常会让描述XSS的分类,以及各自的防御方案。XSS包含三大类:存储型、反射型、DOM型。区分它们的关键是恶意脚本的来源和注入位置:存储型和反射型都是服务端漏洞,只是存储型是持久化的,反射型是拼接在URL参数里的;DOM型则是前端JavaScript直接操作DOM时造成的。
防御措施要从多个层面作答。输出编码是第一步,对不同上下文(HTML标签、属性、JavaScript、CSS)做对应编码;使用CSP(内容安全策略)限制脚本来源;设置HttpOnly防止Cookie被脚本窃取。我当年吃到亏的一点是:只回答了“过滤输入和转义输出”,忽略了CSP。阅卷人如果说“仅靠过滤是不够的”,那其实是在提示你,XSS的防御必须站在纵深防御的角度,不能靠单一的防护手段,这个思路是答安全题的底层逻辑。
4.2 CSRF的攻击流程与token机制
CSRF(跨站请求伪造)也是安全题的常客。理解CSRF的前提是明白浏览器的一个行为:只要Cookie没过期,请求会自动带上它。所以攻击者可以构造一个看似合法的请求,诱导用户在不知情的情况下提交到目标站点。
防御手段中的核心是CSRF Token机制:服务端在页面渲染时生成一个随机token,前端发起请求时带上这个token,服务端校验。需要注意的是,现在很多项目也依赖SameSiteCookie 属性来缓解CSRF,比如配置SameSite=Lax,但我会建议在答题时说明它的限制——有些跨站场景依然无法完全覆盖。
奇安信的题中如果出现CSRF,很可能还会追问“前端在CSRF防御中能做什么”。常规答案是:在请求头中手动加入自定义字段,服务端校验该字段,因为浏览器跨域时带自定义header会触发预检请求,攻击者无法在不经过预检的情况下成功发起恶意请求。
4.3 输入校验与路径遍历的隐患
热词列表里有“输入验证:路径遍历”这样的关键词,说明当年的试题甚至包含了一些偏后端的安全概念。路径遍历(Path Traversal)是指攻击者通过构造包含../的路径来访问服务器上的任意文件,比如../../../etc/passwd。
虽然这是偏后端的漏洞,但前端也可能参与其中。如果前端允许用户上传文件或输入文件路径,并且直接传给后端处理,就要考虑路径规范化的问题。答题时可以提到:前端要对用户输入做白名单校验,禁止包含../或绝对路径等危险信息;更重要的,后端必须做最终的路径规范化,不能被前端的校验替代。
这一题给我们的启示是:安全公司的前端面试,会考察你是否具备“安全开发”的全局视角,而不仅仅是页面写得好不好。
5. 框架与工程化:Vue 2与webpack
5.1 Vue 2响应式原理与虚拟DOM
2019年Vue 2还是绝对主流,奇安信的试题里也考了Vue 2的响应式原理。核心考察点是Object.defineProperty如何使用,以及它的两个短板:无法监听数组的变化(需要通过重写数组方法解决)、无法监听对象属性的新增和删除(需要使用Vue.set和Vue.delete)。
答题时建议按“数据劫持 → 依赖收集 → 派发更新”的逻辑讲:组件渲染时,每个数据对象的属性都有对应的Dep,组件渲染会触发属性的getter,把 Watcher 收集起来;数据变化时,触发setter,通知所有依赖它的 Watcher 更新。虚拟DOM的价值在于:数据变化后不直接操作真实DOM,而是通过 diff 计算出真正需要修改的部分。
如果面试官追问 key 的作用,可以这样答:key 主要用来标识节点身份,帮助 diff 算法判断节点是复用还是重建。列表顺序变化时,如果没有唯一key,diff 会认为节点类型没变,但内容变了,会执行很多无谓的更新;加上 key 之后,Vue 可以精确地移动或复用节点。
5.2 webpack loader与plugin的区别
工程化方向,奇安信2019年的试题里出现过“loader和plugin的区别”这种基础概念题。loader是文件转换器,比如把 ES6+ 转成 ES5,把 Vue 单文件组件转成 JavaScript 模块;plugin则是在webpack整个构建过程的特定时机执行扩展逻辑,比如HtmlWebpackPlugin会在输出阶段自动生成HTML文件。
回答这个问题,可以举一个落地的例子:项目里需要把资源路径加上CDN前缀,这个时候用output.publicPath配置即可;但如果我们还想在构建完成后把多余的文件清理掉,就需要CleanWebpackPlugin这种plugin来处理整个输出流程。整理成一句话:loader作用在“文件”级别,plugin作用在“流程”级别。
构建优化方面,当年比较常用的是happypack或thread-loader做多线程打包、DllPlugin预编译公共库、purgecss做无用CSS清理。现在webpack版本已经迭代很多次,但这些思路依然有效——优化的本质无非是缓存、并发、按需引入、减少搜索范围这几类。
5.3 前端工程化:代码规范与项目骨架
奇安信的业务大多涉及政企客户,代码质量要求会比一般To C公司更高。这在试题中也隐约体现出来:会有简答题问“你们项目的代码规范是怎么落地的”,或者“Vue项目如何组织目录结构更利于维护”。
这个问题没有标准答案,但答题时要能说出具体的工具链和执行流程。比如用 ESLint + Prettier + husky + lint-staged 的组合:在 commit 前自动对暂存区的代码做校验和格式化,能有效保证多人协作时风格统一。再比如目录结构上,按模块划分业务代码,把公共组件、页面、状态管理、工具函数分开,同时通过别名避免深层相对路径的混乱。
真实项目中,这些工具组合的落地并不简单。ESLint 和 Prettier 的规则如果不提前统一,会出现冲突;husky 安装后需要配置 Hooks 路径;lint-staged 如果配置过滤条件写错,可能直接把所有文件都跑一遍。这些细节在作答时提一下,会让面试官觉得你不是只会写demo。
6. 常见问题与答题技巧实录
6.1 答题顺序与时间分配策略
奇安信2019春招的笔试题量不小,我记得现场做题的时候,时间是比较紧的。我的经验是:先快速浏览所有题目,优先做“一眼就知道答案”的选择题和填空题,把该拿的分先拿稳;然后做手写代码题,这类题分值高,即使写不全也能拿步骤分;最后再做简答题和场景设计题。
有些同学容易在简答题上花太多时间,试图把每个概念都写得非常完美,结果后面的手写题没有时间做。手写题的分数权重通常更高,写出一版可运行的防抖函数,比把XSS原理写三段更有用。所以如果时间不够,简答题可以答要点、列出关键词,不要纠结措辞。
6.2 笔试中常见的丢分点复盘
从我自己和后来帮同学复盘的情况看,丢分点主要集中在几个地方。
- 手写防抖节流时,忘记处理
this和args,导致原函数在错误上下文执行; - 深拷贝只处理普通对象和数组,没考虑循环引用,直接栈溢出;
- 事件循环输出题没有区分微任务和宏任务,尤其是
await后面的代码执行时机; - Vue响应式题目用了新术语“Proxy”解释,但对 Object.defineProperty 的局限说得不够清楚;
- webpack题把loader和plugin混为一谈,这在阅卷时是非常明确的扣分点。
丢分并不可怕,可怕的是不知道自己丢在哪。我一般建议候选人这样自查:做完笔试题后,不要急着交卷,把每道题的答案在脑子里过一遍,如果是代码题,试着把变量名、边界条件重新读一遍,很多低级错误能在这一步被发现。
6.3 面试现场追问与沟通技巧
笔试题只是第一关,后面的技术面通常会围绕笔试内容展开追问。我就遇到过这样的情况:笔试写了防抖函数,面试官追问“如果我想让节流第一次触发时立即执行,应该怎么写?”这实际上是在考你对节流背后的开关机制是否理解,而不是只看你是否背出了代码。
我的建议是:面试时不要背诵答案,而是用“问题拆解”的方式回答。先说思路,再讲实现,最后补充边界条件的处理。哪怕某个知识点记不全,也可以说“我记得大致原理是XX,但具体API我有些记不清了,能不能让我再想想”。坦诚比假装自信更受认可,尤其是在技术面试这个场景下。
奇安信这类安全公司的面试官,通常比较看重候选人的“安全感”。所谓“安全感”,就是回答问题的时候,你让面试官觉得你的知识边界是清晰的:哪些是你确定的,哪些你了解过但没用过,哪些完全不会,你能说清楚。这一点,往往比一道题的答案完整还要加分。
6.4 综合场景题:安全信息系统的前端架构
奇安信的试题里还有一种让我印象深刻的场景题:假设我们要做一个安全运营中心的管理系统,模块包括告警列表、用户权限管理、大屏展示、报表导出,请描述前端的整体架构方案。这种题没有标准答案,但考核点非常明确——有没有全局思维。
我当时是分四个维度回答的:
- 工程层面:使用Vue全家桶,按业务模块划分路由和store,组件库用ElementUI做二次封装;
- 权限层面:前端路由守卫加动态路由,结合用户角色配置菜单和操作按钮,后端接口同样校验权限;
- 数据层面:列表数据使用分页和懒加载,大屏数据通过WebSocket推送增量更新,报表下载用Blob处理流文件;
- 安全层面:对导出接口返回的数据做校验,防止越权导出;前端对用户的输入做白名单管控,并对富文本内容做XSS转义。
这种回答的价值在于,它让面试官看到你能把一个复杂系统拆解成可落地的执行方案,而不是只会写局部代码。后来我做技术评审和招聘时,也会用这道题来考察候选人的系统设计能力。
个人经验收尾
回头再看奇安信2019春招这套题,它其实代表了一类“技术向+业务向”结合的出题风格:基础题考功底,原理题考深度,安全题考行业理解,场景题考全局视野。它不简单,但也是真的能筛出人。
我在实际工作中最大的体会是:面试题里那些花里胡哨的知识点,最后都会落在日常开发的琐碎里。比如你理解了深拷贝的循环引用,就会在写缓存工具时想到WeakMap;你理解事件循环,就不会再把所有异步问题都用setTimeout糊弄过去;你理解XSS的深层原理,就不会随便在业务代码里拼HTML字符串。
所以我的建议很简单:不管是准备面试还是日常提升,别把时间都花在刷“题目印象”上。把奇安信这套试题当作一个知识体检单,逐项检查自己哪些是真懂、哪些只是听说过,然后回到原理层面补课。基础这层护城河,挖得越深,越不会被替代。