1. 整体流程回顾:360技术面的轮次安排与考察逻辑
1.1 三轮技术面+一轮HR的真实节奏
先说结论:360前端岗的技术面通常是三轮,外加一轮HR面,整体节奏比我预想的要紧凑。我面的这个岗位base在北京,走的是内推渠道,从第一轮技术面到HR面结束,前后大约两周时间,中间每轮间隔三到四天,给了比较充分的准备缓冲。
第一轮面试官偏年轻,应该是组里的一线开发,问的问题集中在JavaScript基础、CSS布局以及简单的算法题,时长约50分钟。第二轮面试官明显更资深,开场先让我自我介绍,然后直接进入项目深挖,紧接着追问了一堆Vue源码层和工程化相关的问题,时长约70分钟。第三轮是技术终面,更像是综合能力考察,既有偏向系统设计的开放题,也穿插了一些场景类的软性问题,面试官应该是团队leader或者技术总监级别。
HR面放在最后一轮,除了常规的离职原因、薪资期望之外,还问了不少关于团队协作、遇到冲突如何处理的场景题。整体来说,360的面试流程规范,每轮的考察目标很清晰。值得提醒的是,每一轮都会有手写代码的环节,所以光会聊原理是不够的,代码基本功必须扎实。
1.2 每轮面试官的考察重心差异
把三轮面试对比着看,你会发现考察逻辑是有递进关系的。
第一轮属于"广度覆盖",考察面铺得很开,从事件循环、原型链到盒模型、flex布局,再到一道简单的数组去重,说白了就是确认你基础扎不扎实。这个阶段不会问得太深,但涉及的知识点特别密,基本是连环炮式的提问,回答稍有含糊,面试官就会追下一个问题。
第二轮是"深度渗透"。面试官会抓住你简历上写的某个项目,不断往下挖,比如你说用了Vue,他会追问响应式的实现细节、nextTick的原理、diff算法的优化策略,再往后甚至延伸到编译器的优化思路。如果你能接住这层追问,他会继续往工程化方向带,比如webpack的构建流程、代码分割的策略、微前端在你们项目里的落地方式。
第三轮的风格又不一样,更偏向"系统视角"和"综合素质"。我记得面试官问了一个大文件上传的设计题,要求从前端方案、后端配合、异常处理到用户体验优化全部说清楚。这一轮其实不是为了考察某个具体的API或者语法,而是看你能不能站在更高的维度思考问题,有没有全局意识。
2. JavaScript考点逐层拆解:原型链、闭包与Promise的手写深度
2.1 原型链与继承:面试官让我现场推导一套完整的继承方案
第一轮面试开场没多久,面试官就抛出了一个非常经典的问题:"说说JavaScript中原型链是什么?class的继承本质是什么?"
这个问题本身不新鲜,但关键在后面的追问。我背过标准的定义,把__proto__和prototype的关系讲了一遍,面试官点了点头,接着就出了一个代码题:
function Parent(name) { this.name = name; } Parent.prototype.sayName = function() { console.log(this.name); }; function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child;他让我解释这段代码的每一行在干什么,以及为什么Child.prototype要用Object.create(Parent.prototype),而不是直接赋值成Parent.prototype或者用new Parent()。
说实话,这个知识点我准备过,但面试官要求逐行解释时,还是要反应快。我当时的回答思路是:直接赋值Parent.prototype会导致子类和父类共享同一个原型对象,子类若修改原型方法会污染父类;用new Parent()则会多出不必要的实例属性。Object.create生成了一个以Parent.prototype为原型的干净对象,既不执行构造函数,又保持了原型链的正确指向。这还没完,他又追问了ES6 class的super()底层做了什么,本质上就是Parent.call(this, name)加上原型链绑定的语法糖。
这里我给准备面试的朋友一个建议:不要只是背结论,要把原型链的内存图在脑子里画出来,理解每个对象之间的__proto__指向。面试官一旦往深处问,纯背题是扛不住的。
2.2 Promise的手写实现:从then链到微任务的执行顺序
手写Promise几乎是前端中高级面试的家常菜,360也不例外。第一轮面试官给的题目很直接:"用原生JS实现一个Promise,支持then的链式调用。"
我一开始以为写个简化版就够了,但面试官的要求更细:必须处理then的回调返回Promise的情况,并且要保证异步执行的语义。这就涉及几个关键点:
第一,resolve之后注册的then回调必须以异步方式执行。在浏览器环境里就是用MutationObserver或者queueMicrotask来模拟微任务。我用的是queueMicrotask,实现小而清晰。
第二,then方法必须返回一个新的Promise,实现链式调用的关键就在这里。我当时的简化实现是这样的:
class MyPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach(fn => fn()); } }; // ... reject 类似 try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { const promise2 = new MyPromise((resolve, reject) => { const handle = () => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }; if (this.state === 'fulfilled') { queueMicrotask(handle); } else if (this.state === 'pending') { this.onFulfilledCallbacks.push(() => queueMicrotask(handle)); } // ... 其他状态类似 }); return promise2; } }面试官看完之后没有纠结细节,转而问了一个执行顺序的题目:
Promise.resolve() .then(() => { console.log(1); }) .then(() => { console.log(2); }); setTimeout(() => console.log(3), 0); queueMicrotask(() => console.log(4));答案是1、4、2、3。这个如果对微任务队列的理解不深,很容易答错。关键点是每个then都会产生一个新的微任务,所以第二个then的回调必须等第一个then的回调执行完、新的Promise状态变化后才进入微任务队列。
2.3 闭包与作用域:一道代码输出题引发的思考
第二轮面试官上来先给了一道代码输出题,看起来很基础,但暗藏了几个踩坑点:
for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 1000); }输出是5个5,这算是送分题。然后他改了条件:
for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 1000); }输出是0到4,这也是常规操作。但面试官的追问很有意思:"你能说说let在这里到底做了什么,让每次循环都保留了独立的i吗?"
这个点很多人会背"块级作用域"的结论,但讲不清底层机制。我当时的理解是:let声明的变量在每次迭代中都会创建一个新的词法环境,循环体内部对i的引用指向的是当前迭代的词法环境中的那个绑定。本质上,for循环的每次迭代都相当于把let i重新绑定到了一个新的环境下。这是一层理解,但要看穿这层机制,建议去了解for (let ...)在规范里的特殊语义,它并不是简单地把var替换成let。
面试官接着又问:"如果必须用var,有几种方式让输出变成0到4?"这个就简单了,IIFE传参、闭包返回值、.bind传参都可以。这一套组合拳下来,考察的就是你对作用域和执行上下文的真正理解,而不是会背答案。
3. 框架与工程化:Vue响应式原理、diff算法与微前端实践
3.1 Vue2与Vue3响应式实现对比:从defineProperty到Proxy
题库里关于Vue的问题,360问得相当细。第二轮面试官问了一个让很多人头疼的问题:"Vue2的响应式为什么在Vue3中被Proxy替代?Proxy解决了哪些defineProperty做不到的问题?"
标准的回答就是三点:第一,Object.defineProperty只能劫持已有属性,新增和删除属性检测不到,所以Vue2需要用Vue.set和Vue.delete来弥补;第二,defineProperty需要递归遍历对象的所有属性,深层对象初始化性能差;第三,对于数组,需要通过重写数组的push、pop等方法来实现拦截,显得很间接。
但面试官显然不满足于这个层面的回答,他继续追问:"Proxy的性能是不是一定比defineProperty好?"
这个问题问得就很有水平了。实际上,Proxy在惰性劫持方面有优势——只有访问到某个属性时才需要拦截,但Proxy本身对每个操作都有额外开销,不能武断地说Proxy一定更快。Vue3之所以选择Proxy,更多是因为它在能力和代码复杂度上好于defineProperty方案,而不是纯粹的性能优势。
我在回答的时候补了一个关键细节:Vue3的reactive实现还配合了WeakMap缓存,用来存储对象对应的代理,避免同一个对象被重复代理。另外,effect的依赖收集机制用到了track和trigger两级结构,收集的是"属性级"的依赖,而不是组件级的,所以更新粒度更细。
3.2 diff算法与key的作用:为什么列表更新不能用index做key
这个老生常谈的问题,我在面试前觉得已经吃透了,但实际被问到的时候还是发现有些细节没有串起来。面试官的问题是:"Vue更新列表时diff算法是怎么做复用的?key的作用到底是什么?为什么不能滥用index作为key?"
我按照源码的理解回答了整个diff流程:新旧vnode数组会分别用两个头尾指针开始对比,首先处理sameVnode的情况,然后处理新增、删除、移动节点的情况。当不能通过头尾指针直接匹配时,会用key建立旧节点的映射表,通过key找到可以复用的节点。
关于index作为key的问题,我举了一个实战场景:一个列表用index作为key,当你在列表头部插入一条数据时,Vue会认为原来的每一项内容没有变化(因为key还是0、1、2),但实际上每项的内容已经全部错位了。如果列表项有内部状态,比如input输入框、checkbox选中状态、以及子组件自身的数据,就会产生状态错乱。
面试官听完后点了点头,然后问了一个稍微进阶的:"如果列表项本身只是纯展示的文本,用index做key会有什么明显问题吗?"答案是"基本不会"。因为纯文本节点每次patch都会比较文本内容并更新。所以"不能用index做key"这个结论不是绝对的,主要看列表项是否承载了需要复用的内部状态。能把这个边界说清楚,面试官会觉得你真懂,而不是死记硬背。
3.3 微前端:面试官关注的不只是概念,而是落地细节
因为360内部有多个中后台产品线,很多项目都在往微前端架构迁移,所以微前端成了面试中很难避开的话题。第二轮的追问中,面试官专门问到了:"你们做微前端的时候,子应用之间的样式隔离和全局变量污染是怎么处理的?"
我提到用的是single-spa或者qiankun方案,面试官紧接着问了一个很细的点:"qiankun的JS沙箱有几种模式?分别适用于什么场景?"
这个知识点要是不熟很容易答偏。我整理了一下:qiankun的沙箱方案分为三种。LegacySandbox是基于Proxy的快照差异型沙箱,适用于单实例场景;ProxySandbox也是基于Proxy,但每个子应用都有自己独立的代理对象,支持多实例;SnapshotSandbox是qiankun早期方案,通过记录window对象在激活前后的快照差异来还原环境,兼容性最好但不支持多实例。
面试官并没有停留在沙箱模式,而是继续往深挖:"样式隔离如果不用shadow DOM,qiankun是怎么做的?子应用的样式泄漏到主应用怎么办?"
这里我讲了实际踩过的坑:qiankun默认会将子应用的样式用html标签包裹并设置一个>// main.js const worker = new Worker('/hash-worker.js'); worker.postMessage({ file, chunkSize: 2 * 1024 * 1024 }); worker.onmessage = (e) => { if (e.data.type === 'progress') { updateProgress(e.data.percent); } else if (e.data.type === 'done') { const fileHash = e.data.hash; checkUploaded(fileHash); } };
在Worker内部,用FileReader读取每一个分片的ArrayBuffer,通过累加的方式计算整个文件的hash值。这里有个关键细节:如果文件特别大,不建议一次性把整个文件读进内存再计算hash,而是逐个分片读取、逐片哈希迭代,这样内存占用是可控的。
面试官对这个方案比较满意,还追问了"为什么hash值能唯一标识文件"。我回答:虽然理论上不同的文件可能算出相同的hash(撞hash),但在实际业务中概率极低,可以作为文件级别的唯一标识。如果业务对正确性要求特别高,可以配合文件大小加文件名一起作为辅助标识。这个回答让面试官看到我在考虑概率和业务权衡的问题,而不是机械地套方案。
6. 项目复盘与反问技巧:如何讲述项目经验并给面试官留下印象
6.1 用"问题-冲突-方案-结果"的方式讲项目
第二轮的面试官在深入技术问题之前,先让我挑一个简历上的核心项目做介绍。这里一定要警惕:千万不要平铺直叙地讲"我做了某某系统,用了某某技术栈,实现了某某功能",这是最无趣也最无用的介绍方式。
我用的结构化方式是"背景—矛盾—取舍—量化结果"。比如我讲到之前做的一个数据可视化平台,先交代背景:旧的报表系统每次加载需要8秒以上,业务方天天投诉。然后讲矛盾:数据量大,单接口返回全量数据导致渲染卡顿;但业务方又要求看到的是全维度分析,不能简单用分页解决。接着讲方案:前端引入了按需加载和Web Worker做数据聚合计算,配合虚拟滚动渲染,同时把大数据量请求切成了多个小的并行请求。最后讲结果:首屏加载从8秒降到2.3秒,内存占用降低了约40%。
面试官大概率会追问:"你们是怎么定位到性能瓶颈的?"这个时刻非常关键,你要能说出具体的定位过程,比如Performance面板里看到Long Task,发现是数据处理阶段阻塞了主线程2秒多,于是决定把数据聚合逻辑移到Worker中。这种细节的还原度会让面试官相信你真的做过,而不是包装出来的项目。
6.2 反问环节:如何问出有信息量的问题
技术面最后通常会给你反问的机会。很多候选人会问"公司福利怎么样""加班多不多",这类问题不是不能问,但放在HR面会更合适。在技术面反问阶段,我的经验是从以下两个方向提问题。
第一个方向是业务技术问题,比如"360前端团队目前最大的技术挑战是什么""团队在微前端落地过程中遇到的最大难题是什么"。这个问题隐含了你对团队技术方向的好奇,也能从中判断这个团队是偏向业务交付还是基础设施建设。
第二个方向是团队协作和工作方式,比如"前端和后端联动的CI/CD流程是怎么设计的""代码review是怎么开展的"。这类问题能让你了解团队的工程文化和成熟度,一个注重工程质量、有完善review流程的团队,通常更值得加入。
我在这一轮问的是"团队目前对Vue3和React的技术栈选择是怎么考虑的",面试官很认真地回答了他们正在从Vue2迁移到Vue3,同时微前端基座也正在从自研方案往更成熟的社区方案演进。这个回答让我对团队的技术未来有了清晰的预判。
6.3 软性能力:协作冲突与跨部门沟通的应对
第三轮还出现了一个意料之外的问题:"你和产品经理对需求理解有分歧,怎么处理?"
这不是一个技术问题,但答得好能极大加分。我的回答涵盖了三个层次:第一,先对齐目标和用户价值,把分歧点拉回到"这个需求到底服务什么场景、解决用户什么痛点"上来讨论;第二,用数据或原型来说话,如果你觉得某个交互方案体验不好,做一个简易原型或者测一轮用户数据作为佐证;第三,如果仍然无法达成一致,在不影响核心价值的前提下,可以先按产品经理的方案实现一个最小版本,上线后看数据,再决定是否调整。
面试官听完之后补充了一句:"很多前端候选人技术很强,但沟通协作能力是短板,你能说出这样一套处理流程,说明在真实项目里确实经历过类似的冲突。"这句话说明,软性能力在技术终面中的权重并不低。
7. 复盘与建议:这次面试暴露出的薄弱点与针对性补强方向
7.1 我这次面试踩过的坑
虽然整体面试结果比较顺利,但复盘之后我还是发现了一些值得记录的问题。
第一个坑是手写代码时过于依赖记忆。在实现Promise的时候,我虽然搭出了主框架,但在处理resolvePromise的边界情况时有点卡壳——特别是当then回调返回的对象恰好是promise2自身的时候,需要抛出一个TypeError。这个细节我在准备时知道,但一到面试现场,脑子里想的是先把手写Promise的大框架搭出来,细节部分反而被忽略了。事后想想,这种错误很可惜,因为框架正确但边界条件缺失,在严格的面试官眼里会扣不少分。
第二个坑是在讲项目时容易陷入"报菜名"式的技术罗列,比如"我用了Vue3、webpack5、微前端、Web Worker"这一连串名词,但面试官追问某项技术的动机和落地细节时,第一反应却是在回忆文档而不是讲自己的权衡过程。后来我把项目的讲述方式从"技术清单"调整为"问题驱动",效果明显好了很多。建议准备面试的各位,把项目里的每个关键技术点都当成一个"为什么"来准备,而不是一个"用了什么"的答案。
第三个坑是算法题的边界测试不够。360的算法题不会出特别难的动态规划,更多是考察逻辑缜密程度的题目,比如数组去重、两数之和、链表的反转这一类。但面试官会在你写完代码后要求你手动跑测试用例,我在第一轮写链表反转时,没有主动考虑空链表和单节点的情况,面试官提醒之后才补上。这个问题暴露了我"写完就完事"的习惯,实际上每一道手写代码题都应该有防御性编程的意识。
7.2 后续补强计划与准备建议
面完之后我给自己定了一个补强计划,核心有四个方向。
一是把JS基础的知识体系重新用"源码+手写"的方式过一遍,不只是背概念,而是把new、call、apply、bind、debounce、throttle、Promise、EventEmitter这些经典的手写实现全部默写熟练,做到任何一道都能在两三分钟内落笔。
二是框架源码阅读要有产出。光看源码不产出等于白看,我的做法是每读一个模块就写一篇笔记,梳理核心的数据结构、调用链和设计原因。比如Vue3响应式模块,要能画出effect、dep、track、trigger之间的关系,并能解释为什么依赖收集要设计成属性级而不是组件级。
三是工程化工具的配置实战要跟上。webpack的loader和plugin机制、Vite的预构建原理、ESBuild为什么快,这些不只是概念,要能自己搭一个小项目实际操作一遍。360的面试官明显对微前端、构建优化这些工程化问题有实际经验,所以回答的时候一定要带着实践细节,不能停留在"用过"层面。
四是要多做模拟面试,特别是追问环节的模拟。很多时候我们准备的是"标准答案",但面试官真正考察的是你能否在压力下快速思考、条理清晰地回答问题。找朋友或者使用AI面试工具进行多轮模拟,对提升临场反应有很大帮助。
回到"360前端面经"这个话题,除了技术准备之外,我对这家公司的整体印象是:团队风格偏务实,面试官不刁难人,但问题密度高、追问深,非常看重候选人的真实项目落地能力和对技术原理的本质理解。如果你有意向投360的前端岗位,建议把精力重点放在JS基础的手写实现、Vue/React源码深度和工程化实战这三个方向,同时复盘好自己项目里每一个"技术选型背后的为什么"。面试更像是一场高质量的技术交流,把心态从"被考察"调整为"双向了解和展示",发挥会更稳定。