夜雨聆风学习资料网

ARTICLE · 1127584

MetaApp 前端 AI 面

MetaApp 前端 AI 面
先说结论:这是一场没有真人面试官的面试。屏幕里只有一个 AI,全程 36 分钟,摄像头和麦克风都开着。最特别的一点是它会顺着你的回答追问。你提了什么,它就可能往哪儿挖;简历上写了什么,它也可能直接拎出来问。所以这不是一场“背完题库就能过”的面试——你说出去的话,得接得住。这篇把考到的题目和我整理的答题要点都记下来,正在找实习的同学可以直接拿去对照。

一、这场 AI 面试是怎么考的

用的是北森的 AI 面试系统,有几个硬性设定值得提前知道:

▪︎ 总时长 36 分钟,没有真人面试官,全程录像录音

▪︎ 每道题 180 秒作答,另有 20 秒读题时间

▪︎ 自适应出题:AI 会根据你前面的回答调整后面的方向

▪︎ 简历会被追问:除了系统里的题,还会针对简历上的内容提问

二、八股真题 + 答题要点

下面是这次考到的题,以及每题我整理的答题要点。

2.1 箭头函数和普通函数的区别

核心差异是四件事:

▪︎  this:箭头函数没有自己的 this,它在定义时就捕获外层作用域的 this,而且用 call / apply / bind 也改不了;普通函数的 this 由调用方式决定(默认绑定、隐式绑定、显式绑定、new 绑定)。

▪︎  能不能 new:箭头函数没有 [[Construct]],当构造函数用会直接报错,它也没有 prototype 属性。

▪︎  arguments:箭头函数没有自己的 arguments,要用 rest 参数代替。

▪︎  其他:箭头函数不能做 Generator(不能用 yield),也没有自己的 super 和 new.target。

适用场景:回调、需要在回调里保留外层 this 的地方(比如 setTimeout、数组方法)。

不适用的场景:对象的方法(this 不会指向这个对象)、需要动态 this 的场景、构造函数、原型方法。

加分点:箭头函数的 this 是“词法”决定的,跟运行时无关——所以就算你 bind 它,也改不了。

2.2 什么是事件委托,有什么好处

定义:不把监听器绑到每个子元素上,而是绑在它们的父元素上,利用事件冒泡,在父元素里统一处理,再用 event.target 判断到底是哪个子元素触发的。

三个好处:

▪︎  省内存、少监听器:一个长列表如果每个 li 都绑一次,就是几百个监听器;委托只需要一个。

▪︎  动态元素自动生效:后面新增的子元素不用重新绑定。

▪︎  逻辑集中:所有相关处理都在一处,好维护。

要注意的边界:不是所有事件都冒泡(focus / blur 不冒泡,得用 focusin / focusout);event.target 可能是很深的子节点,通常要配 closest() 找真正的目标;如果你在子元素里阻止了冒泡,委托就失效了。

2.3 讲讲浏览器的事件冒泡机制

事件流分三个阶段:捕获 → 目标 → 冒泡。

捕获阶段:从 window / document 一路往下传到目标元素; 目标阶段:事件到达目标元素; 冒泡阶段:从目标元素一路往上回到 document / window。

addEventListener 的第三个参数决定了你在哪个阶段监听:true 或 {capture: true} 是在捕获阶段。

三个容易混的 API,一定要分清:

▪︎  stopPropagation():阻止事件继续传播(捕获和冒泡都会被截断);

▪︎  stopImmediatePropagation():连当前元素上后注册的监听器也一起阻止;

▪︎  preventDefault():阻止的是默认行为,跟传播没关系,两者不要混。

两个容易混的属性:event.target 是实际触发事件的元素,event.currentTarget 是当前监听器绑定的元素(非箭头函数里 this 就等于它)。

不冒泡的事件:focus、blur、mouseenter、mouseleave、load 等;其中 focus / blur 可以用 focusin / focusout 替代。

2.4 什么是内存泄漏,什么情况下会造成内存泄漏

定义:对象已经不再被使用了,但因为还有引用能到达它(可达),垃圾回收器不敢回收,这块内存就一直占着。

常见成因(按出现频率排):

▪︎  意外的全局变量:忘记声明就赋值,或者 this 指向了 window;

▪︎  没清掉的定时器:setInterval 一直在跑,回调里又引用着大对象;

▪︎  没解绑的事件监听:尤其是绑在 window / document 上的,组件卸载了没 removeEventListener;React 里就是 useEffect 没写清理函数;

▪︎  闭包持有大对象:闭包把外层的变量一直留住;

▪︎ DOM 引用泄漏:节点已经从页面移除了,但 JS 里还留着引用(detached DOM),反过来也成立;

▪︎  缓存只进不出:用 Map 或数组当缓存,没有淘汰策略;

▪︎  没取消的订阅:WebSocket、EventBus、没取消的请求。

怎么定位:Chrome DevTools 的 Memory 面板做堆快照对比,看两次快照间“该消失却没消失”的对象;也可以开 Performance monitor 看内存曲线是不是一路往上不回落。

2.5 垃圾回收机制的算法有哪些

引用计数:记录每个对象被引用的次数,归零就回收。致命缺陷是循环引用——两个对象互相引用,计数永远不为零,就漏了。这是 IE 时代内存泄漏的经典来源,现在基本不用它了。

标记-清除:从根(全局对象、调用栈)出发,把所有能到达的对象标记出来,没被标记的就是垃圾,清掉。它解决了循环引用问题,是现在的主流思路(V8 就用这个思路)。

标记-整理:标记完之后,把存活对象往一端挪紧,清掉碎片——解决标记-清除留下的内存碎片问题。

分代回收:按对象活得久不久分成新生代和老生代,用不同策略:

▪︎  新生代用 Scavenge(Cheney 算法):把空间分成 from / to 两块,存活对象复制到 to,然后交换;活得够久的那批会被“晋升”到老生代;

▪︎  老生代用标记-清除 / 标记-整理,配合增量标记把一次长停顿拆成多次短停顿,避免卡顿。

一个容易被忽略的点:JS 的 GC 是非确定性的,你没法精确知道它什么时候回收;想让某个引用“不阻止回收”,就用 WeakMap / WeakSet。

2.6 讲一讲 addEventListener

基本语法:target.addEventListener(type, listener, options),第三个参数既可以是布尔值(是否捕获),也可以是一个选项对象。

选项对象里最实用的三个:

▪︎  capture:在捕获阶段监听;

▪︎  once: true:触发一次后自动移除,省掉手动解绑;

▪︎ passive: true:承诺回调里不会调用 preventDefault(),浏览器就能立刻滚动,在 touchmove / wheel 这类高频事件里对滚动性能提升很明显。

和 onclick 的区别:onclick 一个元素只能绑一个(后写的覆盖前面的);addEventListener 可以绑多个,按注册顺序执行,而且能选捕获还是冒泡。

移除时的坑:removeEventListener 必须传同一个函数引用、并且 capture 选项要一致,所以匿名函数是移除不掉的——这也是为什么回调最好具名或者存起来。

加分点:可以用 AbortSignal 一次性移除一批监听器——把 { signal: controller.signal } 传进去,之后 controller.abort() 就能全部解绑。

2.7 new 经过了哪些过程操作

四步:

▪︎  建对象:创建一个新对象,并把它和构造函数的 prototype 关联起来(原型指向它);

▪︎  绑 this:让构造函数里的 this 指向这个新对象;

▪︎  执行:跑构造函数里的代码,给 this 加属性;

▪︎  返回:如果构造函数显式返回了一个对象,就返回那个对象;否则忽略返回值,返回这个新对象(返回原始值会被忽略)。

手写实现就三句话:Object.create(Ctor.prototype) 建对象 → Ctor.apply(obj, args) 执行构造函数 → 判断返回值是不是对象,是就返回它,否则返回 obj。

加分点:箭头函数不能被 new,因为它没有 [[Construct]];另外 prototype 上的 constructor 属性会指回构造函数本身。

三、行为题

3.1 近两三年,说说超越自我的一件事,过程怎么样,到达什么效果

这道题的判分点其实是“超越”两个字,所以选材比表达更重要:▪︎ 起点要低:得先讲清楚“原来的我会到哪、原来的预期是什么”,后面的超越才成立;▪︎ 要有难点和转折:一路顺风顺水的事,撑不起“超越自我”这个题眼;▪︎ 必须有量化结果:没有数字的成果,在面试官眼里约等于平淡;▪︎ 必须有你个人不可替代的动作:别把团队的功劳说成自己的。表达上,我会把“结果”讲成对比:不是“我做到了 X”,而是“原来要 A 时间 / A 方式,现在变成 B”——让超越看得见。

示例:

情境:大二那年,我连一个完整项目都没独立做过,课程作业基本都是跟着教程一步步敲。

目标:学院要做一个“实验室设备预约”的小系统,一直没人接。我把它接了下来,目标是在两周内独立交付一个真能跑、能给人用的系统——对一个没写过真实项目的人来说,这本身就超出预期。

行动:我把两周切成三段——前 3 天啃框架文档和数据库设计,中间 6 天写功能,最后 5 天联调修 bug。中途卡在“同一时段被两个人约走”的并发问题上,我没去找现成答案,而是回头读了一遍官方文档里的锁和事务,最后用乐观锁加唯一索引解决。每天晚上我都把进度和卡点记进一个文档,防止自己跑偏。

结果:系统按期上线,被 3 个实验室、前后约 200 名同学用了一整个学期。后来我把踩过的坑整理成一份部署文档,成了下一届学弟学妹的直接参考。

收尾:从“跟着教程走”到“独立交付一个真被人用的系统”,这是我这两三年里最大的一次跨越。

3.2 说说你大学中遇到的比较深刻的团队协作,你是如何协调不同的人合作,你用了什么方法,最终怎么样

这道题的题眼是“协调”和“方法”,不是“协作”本身。▪︎ 要有冲突:全是顺风顺水的团队,反而拿不到分。分歧、拖延、能力不均是更好的素材;▪︎ 行动部分要给出具体机制:不是“多沟通”,而是“每周一次同步会 + 看板认领 + 分歧用数据投票”这种能复用的做法;▪︎ 说清你的角色:不一定是队长,但要有别人替代不了的贡献;▪︎ 结果之后加一句反思:如果重来会怎么改——这一步很能拉开差距。

示例:

情境:大二下学期,我们 4 个人组队做一个校园二手交易平台。做到第 3 周时,有两个人几乎没提交过代码,进度明显卡住。
任务:我负责前端,但如果不把团队拉回来,整个项目就要烂尾。
行动:我做了三件事。第一,把“做出平台”这个大目标拆成一条条能认领的小任务,全部挂在共享看板上,每项都写清截止时间和验收标准,任务自己认领、不摊派。第二,改成每周一次 15 分钟站会,只说三件事:上周做完什么、现在卡在哪、下一步做什么。第三,遇到分歧不靠嗓门,把方案写进文档、列清取舍,大家各自打分,选总分最高的那个。
结果:最后 3 周,任务完成率从大约 40% 提到 100%,项目按期结题并拿到院级优秀;原本进度最慢的那位同学,后半程主动承担了后端接口的工作。
反思:如果重来一次,我会在项目一开始就把任务颗粒度定下来,而不是等到出问题才去补。

复盘

第一,考的不是偏题,而是地基。箭头函数的 this、事件流三阶段、内存泄漏、GC 算法、new 的过程——这些不是冷门知识,反而是最该背熟的。前端实习的筛选线,就划在“基础扎不扎实”上。

第二,自适应面试要主动“给钩子”。既然它会顺着你的回答追问,那你的每一句回答其实都在为后面出题。主动提你熟的领域,把追问引到你有利的地方;但别为了显得懂而提你答不上来的东西——那等于自己给自己挖坑。

第三,180 秒要练“有结构地铺满”。定义 → 原理 → 例子 → 边界,这套结构不只适用于这场面试,它其实是所有技术口述题的通解。

最后一点:AI 面试没有表情反馈,你永远不知道对面“满不满意”,所以卡壳的时候不要慌,把问题复述一遍、分点作答,比硬撑着沉默好得多。

一句话总结:这场 AI 面试考的不是你会不会聊天,而是你的基础知识能不能在被追问的情况下站得住。

THE END

相关学习资料