☰
第四范式前端笔试复盘:JavaScript、浏览器与手写代码核心考点解析
2026/10/31 6:40:21 网站建设 项目流程

最近在翻旧资料的时候,看到当年准备秋招时整理的一套第四范式前端笔试复盘笔记,感慨还挺多的。2020年那会儿AI四小龙的招聘热度正高,第四范式的笔试题目在技术社区里讨论度也不低。现在回头看,这套题虽然已经过去几年,但考察的知识点依然很经典——JavaScript基础、浏览器原理、框架实现思路、手写代码能力,基本覆盖了前端面试的核心疆域。今天就把我当时对这套题的拆解和复盘整理出来,希望能给正在准备大厂前端岗位、尤其是AI公司前端岗位的朋友一些参考。

先说个整体感受:第四范式的笔试题量和难度属于中等偏上,但更看重基础扎实度和代码实现能力,题目设计得比较讲究,不是靠背题就能过的。这份复盘完全是基于我个人的回忆和理解,题目描述可能会有细节出入,但考察点和解题思路是通用的,值得好好研究。

1. 这份笔试到底考什么:第四范式前端的出题风格

1.1 先聊两句这家公司和它的技术调性

第四范式是做AI平台和机器学习核心技术的公司,技术社区里对它的普遍认知是“工程师文化浓厚,算法功底扎实”。这种技术背景会直接影响前端团队的招聘偏好——他们招前端不完全只看你能不能写页面,更在意你写代码的底层逻辑是否清晰、遇到复杂问题时能不能有体系化思考。

这套笔试的题目设置也印证了这一点。选择题和简答题里,JavaScript的各种底层机制占了很大比重;编程题更是直接对手写代码能力提出硬性要求。由于AI公司的业务通常涉及大规模数据处理和复杂交互场景,前端需要处理的数据结构、异步流程、性能优化需求都比普通业务团队更多,所以这些方向顺理成章成了考察重点。

1.2 从题型分布看考察重心

从我回忆的情况来看,整套卷子大致分为四个模块:计算机基础与网络、JavaScript核心机制、框架与工程化、在线编程题。每个模块的题量和考察方向可以整理成下面这个表格:

模块常见题型侧重点
网络与浏览器选择题、简答题缓存策略、渲染流程、跨域方案
JavaScript核心选择题、代码输出题数据类型、作用域、事件循环、异步
框架与工程化简答题MVVM原理、虚拟DOM、组件通信、webpack
算法与手写在线编程数组处理、防抖节流、深拷贝、Promise

这个分布透露出的信息很明确:业务开发中经常会用到的能力被反复考察,说明面试官不是想刁难人,而是想筛出真正能干活、能解决实际问题的候选人。如果你平时开发只停留在“调接口、绑数据、写样式”的层面,这套卷子做起来会很吃力;如果你对底层原理有过系统研究,做起来就会顺手很多。

2. 基础题回顾:网络、缓存与浏览器原理

2.1 从输入URL到页面展示,怎么答才不丢分

这类题在各大厂笔试里都是常客,第四范式也考了,不过考察角度稍微刁钻一些。直接让你“描述从输入URL到页面展示的过程”是最基础的版本,但第四范式的题目会在其中穿插一些追问,比如“在哪个环节可能发生DNS缓存命中”“渲染过程中JavaScript的加载和执行会阻塞什么”。

如果只是机械地背答案,很容易在追问环节卡壳。我从实际操作出发的建议是:别按一条线从头背到尾,而是按阶段拆开回答。网络请求阶段要说清楚DNS解析、TCP握手、HTTP请求发送;浏览器解析阶段要区分HTML、CSS、JavaScript三种资源的处理顺序;渲染阶段要讲清楚DOM树、CSSOM树、渲染树、布局和绘制的流程。最后一定要提一嘴“JavaScript会阻塞解析,所以才有defer和async”,这一句往往能切中考察者的意图。

我自己的答题经验是:先按阶段说出主干流程,再说每阶段的细节和可能的性能瓶颈。这样显得有框架感,也有深度,面试官听起来会觉得你是真懂而不是背知识点。

2.2 浏览器缓存策略的考察要点

缓存题几乎是前端笔试的必备菜,第四范式的题目主要考察两个维度:一是强缓存和协商缓存的区别,二是实际项目里的应用场景。

强缓存的核心是Cache-Control和Expires,其中Cache-Control的max-age指定了缓存有效期,Expires是绝对时间,优先级低于Cache-Control。协商缓存则依赖Last-Modified/If-Modified-Since和ETag/If-None-Match两对头信息。这里有一个容易混淆的点:Last-Modified只能精确到秒,如果文件在同一个秒级时间内被修改,客户端会拿到错误缓存;ETag则可以更精确地判断文件内容是否变化,所以像Webpack这样的构建工具生成的静态资源,文件名里都会带上hash值,再配合Cache-Control: max-age=31536000实现长缓存。

我从实战角度补充一点:如果在面试题里碰到“为什么刷新页面后请求状态码是304”这类问题,回答时要说清楚强缓存和协商缓存的触发顺序——浏览器先判断强缓存是否命中,缓存有效就直接用本地副本;缓存失效后再发请求,带上If-None-Match或If-Modified-Since头,让服务器判断是否需要重新返回完整内容。这个顺序很容易被搞混,建议用表格对比记忆。

2.3 跨域问题:答题得从“为什么”说起

跨域几乎是我遇到的每一场前端笔试都会出现的考点。第四范式的题目不是简单问“怎么解决跨域”,而是先问你“浏览器为什么要有同源策略”,然后再让你写解决方案。

这两问是递进关系。同源策略保护的其实是用户数据安全,防止恶意网站读取另一个网站的Cookie或接口数据。没有这一层限制,你登录过购物网站后访问一个恶意页面,你的登录态就可能被窃取。理解了这一层,答案就不难组织出来。

解决方案常见的就那么几种:JSONP、CORS、代理转发、postMessage。不过很多人在写CORS的时候只写出Access-Control-Allow-Origin一个响应头,这其实不够。实际开发里还要处理预检请求(OPTIONS)、允许的请求方法、允许的请求头、是否携带凭证等配置。我建议回答CORS时把响应头完整地列出来,再解释简单请求和预检请求的区别,这样能跟只会背概念的人拉开差距。JSONP虽然现在用得少了,但它的原理——动态创建script标签、利用src不受同源策略限制——还是值得写清楚。

我印象里版本比较旧,但考察点至今仍然是高频。如果你现在准备面试,建议把跨域相关的实现都动手写一遍,尤其是CORS的几种配置方式和代理转发的具体配置代码,笔试里很容易让你“写出关键配置代码”。

3. JavaScript核心考点:这些题最拉差距

3.1 数据类型的边界题:typeof、instanceof到底怎么用

第四范式的选择题里有一道这样的题:给出一段代码,判断typeof各种值的输出结果。这类题表面简单,但坑非常多。

typeof null返回"object",这是JavaScript历史遗留的bug,但几乎每次笔试都会考;typeof function(){}返回"function",这是typeof唯一返回"function"的引用类型;typeof NaN返回"number",因为NaN确实是Number类型;还有typeof undeclaredVariable不会报错而是返回"undefined"。

instanceof的考察点则是它的判断原理——沿着原型链查找,看左侧对象的原型链上是否有右侧构造函数的prototype。这里值得多说一句:instanceof的右侧必须是一个对象类型(函数),如果写成基本类型会直接报错;它也不能跨iframe判断,因为不同iframe有各自独立的全局对象。这些边界场景往往是选择题里用来拉开差距的。

我当时的复习方法是把数据类型检测的全部方法做了一个横向对比表,逐一记住它们的使用场景:typeof适合判断基本类型,instanceof适合判断引用类型的具体构造函数,Object.prototype.toString.call()可以准确判断所有内置类型,Array.isArray()专门判断数组。笔试碰到“怎么判断一个变量是不是数组”这类题时,把几种方法对比着回答,深度立刻就有了。

3.2 事件循环:微任务宏任务的执行顺序

这是第四范式笔试里我认为最拉分的一道选择题。题目大概长这样:给出一段混合了setTimeout、Promise、async/await的代码,让你写出最终的输出顺序。这类题考察的就是事件循环机制。

先理清底层逻辑:JavaScript是单线程的,但通过事件循环实现异步非阻塞。执行栈同步代码时,遇到异步任务会先挂起,等时间到了或Promise状态变化了再回调。回调分两类——微任务(Promise.then、MutationObserver、queueMicrotask)和宏任务(setTimeout、setInterval、I/O、UI渲染)。事件循环的每一轮会先清空宏任务队列里的一个任务,然后清空整个微任务队列,再进入下一轮。

这道题的易错点主要集中在:Promise构造函数里的代码是同步执行的,.then()回调才是微任务;async函数里await后面的代码会被包成微任务执行。很多人会把构造函数里的同步代码误认为是异步的,一上来就排错顺序,整道题就全错了。

我建议准备这类题时,不只是背结论,要自己画执行顺序图,把每一行代码的执行时机标出来。我当时把所有常考的异步代码组合都手动跑了一遍,然后在脑内模拟输出过程,选择题基本就稳了。

3.3 闭包与作用域的经典陷阱

闭包题在第四范式的笔试里属于必考题,基本是以“代码输出题”的形式出现。最典型的一道是循环中var声明和定时器结合的题:

for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); }

输出结果是5个5,而不是0、1、2、3、4。原因很简单:var没有块级作用域,i是全局变量,等定时器回调执行的时候,循环早就跑完了,i已经变成了5。

解法有三种:ES6的let声明、立即执行函数包裹、或者Function.prototype.bind传参。但笔试往往不光要你写解法,还会追问闭包的形成原因和内存影响。我记得第四范式有一道简答题就是“谈谈闭包的优缺点”,如果你只写“可以让外部访问函数内部变量”,得分一定不高。完整的回答应该包括:闭包的形成条件(函数嵌套、内部函数引用外部变量)、能实现的功能(数据私有化、延迟执行、函数柯里化)、必须注意的问题(内存泄漏风险——被闭包引用的变量不会被垃圾回收,所以在不需要时要手动置空引用)。

闭包的实际应用在笔试题里经常以“防抖”“节流”“柯里化”的形式出现,这些手写题的核心就是闭包保存状态。所以闭包不是孤立的知识点,而是串联整个JavaScript进阶能力的关键。我建议先把闭包的底层机制彻底搞明白,再去做防抖、节流、柯里化的手写题,思路会清晰很多。

3.4 手写call/apply/bind:考察原型与this绑定

第四范式的笔试题里有一道手写Function.prototype.bind的编程题,这在当年算是比较经典的考察方式。这道题考察的点有两个:一是是否理解bind的实现原理,二是对边界情况的处理能力。

bind的核心实现思路不复杂:返回一个新函数,调用时把原函数作为method挂到指定this对象上执行。但真正写起来有几个坑:首先要区分是当作构造函数调用(new)还是普通函数调用,bind返回的函数如果被new了,this应该是新创建的对象而不是传入的bind对象;其次要考虑原函数本身的length属性是否要保留;还有就是要处理参数拼接——bind传入的参数和调用返回函数时传入的参数要合并。

这道题给我的启发是:笔试里的手写题不是看你背了多少API,而是看你能不能从底层重新实现出来。如果你平时只是用bind,没想过它内部怎么工作,这张卷子就会卡住。我当时做完这道题之后,把call、apply、bind三者的实现都写了一遍,再结合new的优先级去理解this绑定规则,整个JavaScript的函数知识体系就通顺了很多。

4. 框架与工程化:Vue源码级理解题

4.1 Vue响应式原理为什么值得写满一屏

第四范式在框架层面更偏向Vue,考察方式也是“源码理解”级别的。最常见的题目就是:请描述Vue 2.x响应式原理。

答案的核心是Object.defineProperty。Vue在初始化时,会遍历data中的每个属性,通过Object.defineProperty为它们设置getter和setter。getter里做依赖收集——记录谁在用这个数据(也就是Watcher);setter里做派发更新——数据变化时通知Watcher更新视图。每个组件实例对应一个Watcher,当数据变化触发setter时,会通知Watcher去执行更新函数,最终重新渲染组件。

这里容易忽略的细节是:Vue 2的响应式在对象新增属性和删除属性时是检测不到的,所以才有Vue.set和this.$set这两个API。数组的响应式也是通过改写数组的七个方法(push、pop、shift、unshift、splice、sort、reverse)来实现的。

Vue 3把Object.defineProperty换成了Proxy,好处显而易见:可以代理整个对象而不需要遍历每个属性,新增和删除属性也能监听到,性能更好。如果笔试里问“Vue 3为什么用Proxy”,要答出Proxy的代理能力、性能提升、以及解决了Vue 2的哪些限制这三个层面。这道题虽然看起来偏原理,但实际开发中遇到的莫名其妙不更新问题,十有八九都是响应式机制没弄明白。

4.2 虚拟DOM和diff算法的核心逻辑

除了响应式,虚拟DOM也是第四范式简答的高频题。题目一般会问:虚拟DOM是什么?为什么需要它?diff算法是怎么工作的?

虚拟DOM本质就是一个描述真实DOM结构的JavaScript对象,它有type(标签名)、props(属性)、children(子节点)这些字段。有了虚拟DOM,框架可以在内存中用计算速度换取真实DOM的操作次数——因为操作内存中的对象远比操作真实DOM性能好。

diff算法的核心逻辑可以用三个词概括:同层比较、key优化、最小化更新。同层比较指的是只对比同一层级的节点,不跨层级移动;key则用来标记列表中每个节点的身份,方便复用而不是重新创建。所以v-for的时候如果不写key或者乱用index,会导致节点复用错乱,状态出问题。

这道题如果能结合源码细节来回答会更出彩,比如比较新旧VNode时判断sameVNode的条件、patch过程中什么时候复用节点、什么时候直接替换整棵子树。我当时看了一遍Vue 2的patch源码再去做这道简答题,明显感觉内容饱满了很多,而不是干巴巴地背那几条结论。

4.3 组件通信方案:父子、兄弟、跨层怎么选

第四范式的框架题里还出现过一道相对实际的题:组件A和组件B是兄弟组件,需要共享数据,应该怎么做?

这道题没有标准唯一解,关键是看你能列出多少种方案并说出各自的适用场景。父子通信可以用props和$emit;兄弟通信可以借助共同的父组件做中转,或者用事件总线(Vue 2中用空的Vue实例做$on/$emit);跨层级的通信可以用provide/inject,但它是非响应式的;更复杂的场景就要上Vuex或者Pinia这样的全局状态管理库。

我在复盘的时候发现,面试官出这道题其实是想看候选人对工程方案的理解——你平时在项目里遇到类似需求,能不能做出合理的技术选型。如果只会写业务代码,可能就会给出“用一个全局变量存”这种土匪方案。真正合理的回答路径应该是:先分析数据量大小和共享范围,然后给出最小成本的通信方案。这也是笔试和面试的共通点:考察的不只是你会不会,而是你怎么做决策。

4.4 webpack的关键配置与构建优化

工程化部分,第四范式考过webpack相关的问题,主要围绕Loader和Plugin的区别、构建优化手段来展开。

Loader是webpack里用来处理文件转换的,它是一个函数,接收文件内容作为参数并返回转换后的内容,常见的有babel-loader、css-loader、style-loader、file-loader等。Plugin则是用来处理整个构建过程中的任务,比如打包优化、资源管理、环境变量注入,它的能力比Loader更宏观。

构建优化的答题方向一般有这几个层次:代码体积层面(Tree Shaking、代码分割、压缩混淆);构建速度层面(缓存Loader执行结果、多进程构建thread-loader、使用DLLPlugin预编译);资源层面(图片压缩、CDN引入第三方库、polyfill按需加载)。实际开发中最常用的是splitChunks把公共依赖拆出来,以及路由懒加载让首屏少加载资源。

这类题的答案网上有很多整理好的版本,但记忆难度比较大。我的经验是找一个自己真实做过的优化案例,把优化前后的对比数据记下来,回答的时候用自己的项目去说明方案,可信度和深度都会高很多。

5. 编程题实战:手写代码的答题策略

5.1 手写防抖节流,如何答出工程化思维

第四范式的在线编程题里有一道防抖(debounce)的实现,这是前端手写代码题里最经典的一道,也是我能明确回忆起来的一道题目。题目一般会要求“实现一个防抖函数,并说明应用场景”。

防抖的核心是:事件触发后,延迟一段时间执行;如果在这段时间内再次触发,就重新计时。所以返回函数内部要清理之前的定时器。标准实现大概这样:

function debounce(fn, delay) { let timer = null; return function(...args) { const context = this; clearTimeout(timer); timer = setTimeout(() => { fn.apply(context, args); }, delay); }; }

这里有两个关键细节容易丢分:一是this要指向正确的调用对象,所以要用fn.apply(context, args)而不是直接fn(...args);二是要支持传参,所以要用rest参数...args把事件对象等参数透传进去。

应用场景这个问法其实是加分项,回答得好能体现工程经验。最常见的是搜索框输入联想——用户输入过程中连续触发input事件,如果不做防抖,每按一个键就会发一次请求,后端压力大,前端还容易收到乱序的响应。防抖后用户停止输入一段时间再触发请求,体验和性能都更好。类似的还有窗口resize事件、滚动事件配合防抖节流的权衡。我当时在答题时顺便写了一版带immediate参数(是否立即执行一次)、带取消方法的增强版,还解释了在Vue组件里使用防抖时要注意在beforeUnmount里取消防抖定时器以防内存泄漏,这个细节后来复盘时觉得非常加分。节流(throttle)和防抖容易搞混,我的记忆方法是:防抖是“你看完电影再关门”,节流是“每隔一段时间必须开门一次”。

5.2 深拷贝:一题测出你的边界处理能力

深拷贝题比防抖更考验代码的完备性。这类题的好答之处在于,面试官往往会在题目后面标注“注意处理循环引用和Symbol属性”等提示。

最简单的深拷贝一行代码就能写:JSON.parse(JSON.stringify(obj)),但这几个场景会挂:包含undefined、函数、Symbol时这些属性会被直接丢弃;包含Date、RegExp、Map、Set时会被转成普通对象或字符串;包含循环引用时会直接报错。所以真正的深拷贝需要用递归加WeakMap来解决循环引用,用Reflect.ownKeys来遍历所有属性(包括Symbol和不可枚举属性)。

我当时给出的版本核心逻辑是:先判断是不是引用类型,不是就直接返回;然后用WeakMap记录已拷贝的对象,遇到循环引用时直接返回缓存结果;接着用Object.create(Object.getPrototypeOf(target))保留原型链;最后区分普通对象、数组、Date、RegExp、Map、Set分别处理。

这类题想拿高分的关键是考虑周全,而不是写完递归就结束。我在复盘时把边界场景整理成了一个小表格,建议你也这样做:

边界情况JSON方案的表现完善方案的处理
undefined、函数、Symbol属性被丢弃保留原值
Date、RegExp变成普通对象重建对应类型
Map、Set变成空对象逐一拷贝条目
循环引用直接报错WeakMap缓存
原型链丢失原型保留prototype

5.3 数组扁平化与手写Promise:热身高频题

除了上面两道相对进阶的题,第四范式的笔试里也考了比较基础的数组处理和异步流程控制题。

数组扁平化的实现方式很多,我习惯把它拆成不同难度逐层递进:最简单的是arr.flat(Infinity),一行搞定;中等难度是用reduce配合concat递归实现;再难一点就是自己用栈模拟展开。笔试里如果你直接写arr.flat(Infinity)也不能说错,但面试官其实期待看到你能手工实现一遍。这里推荐一个技巧:先用递归实现,然后追问“如果数组非常深,递归会有什么问题”,顺势补充迭代实现的方案,展示你考虑了性能问题。

手写Promise的题目在2020年的前端笔试里已经不算新鲜了,第四范式考的大概率是简化版——实现Promise的基本状态流转、resolve/reject和then方法的链式调用。核心就是状态只能从pending变成fulfilled或rejected,不能逆向变化;then里注册的回调要等状态变化后再执行;链式调用时then的返回值决定下一个then的输入。这道题如果从零开始写全非常耗时,我当时的策略是先搭好骨架,确保状态流转逻辑正确,再补then的处理逻辑,最后处理值的透传,不必追求完整的resolvePromise流程,但状态机的正确性是必须保证的。

5.4 编程题的答题顺序与时间分配建议

这块虽然不是知识点,但我觉得每个经历过笔试的人都会有自己的血泪教训。第四范式的线上笔试时间不算特别宽裕,我当时按照题量和难度预估了一下时间配比:选择题和简答题控制在45%左右,编程题留55%左右,其中深拷贝和手写Promise这种代码量大的题至少要留出30%的时间。

一个我自己踩过坑后总结出的经验:遇到编程题不要立刻敲代码,先把思路写成注释或伪代码。这样做有两个好处,一是帮自己理清逻辑,二是即使代码没写完,面试官看你注释也能看出你的解题思路,有时候能给过程分。另外,在线笔试的IDE通常没有本地那么智能,各种方法名容易拼错,写完一定留时间自己跑一遍测试用例,至少覆盖题目给的示例输入。有一个明确的教训是:代码写完没测试就提交,很可能因为一个拼写错误整题零分,太可惜了。

6. 复盘与提升:前端面试的备考建议

6.1 不同基础阶段的准备重点

准备这类笔试的时候,最忌讳的就是盲目刷题。我先说个真实的体会:如果你对题目背后的原理不清楚,做一百道题也只会一套就忘一套;但如果你把知识体系建起来,题目再怎么变都能应对。

如果你是刚入门不久,建议先把JavaScript核心基础打牢,尤其是作用域、闭包、this指向、异步机制这四块,这是所有前端面试里最基础也最关键的底层能力。然后是浏览器原理,缓存、渲染、跨域、事件循环,这些都是必考板块,要整体过一遍。

如果你已经做了两年业务开发但没系统看过原理,重点可以放在框架源码理解和工程化实践上,认真搞清楚Vue或React的数据驱动流程、虚拟DOM、diff算法,以及webpack构建链路。你平时写业务时遇到的有意思的问题,反而可以成为面试里的加分项,比如“为什么列表更新后页面没变”“为什么组件在某种情况下不重新渲染”,这些真实案例比背概念更能打动面试官。

如果你的基础已经比较扎实,建议重点打磨手写代码能力,不但要能写出来,还要能说出设计思路和边界情况处理。这部分是最能体现代码功底的地方,也是拉开分数差距的关键。

6.2 刷题之外,值得长期打磨的前端基本功

我在复盘第四范式这套笔试题的时候,发现一个现象:很多题之所以做不好,不是因为这些题本身很难,而是因为平时开发时缺少思考的习惯。比如调一个接口时就只是调接口,没想过同源策略、CORS、预检请求;写一个组件时就只是写模板,没想过Vue的数据流是怎么走通的。

所以笔试准备不能只看面试题,更要借这个机会把前端知识体系补齐。我建议你把面试题当成一个地图,每个考点都标注出你自己的掌握程度——熟悉的、模糊的、完全不会的——然后逐项去补。补的时候尽量追到源码层面,比如Vue的响应式,光知道Object.defineProperty是不够的,可以花一下午把相关的源码跟一遍。这个过程虽然费时,但收益是长久的。

还有一点很重要:实际动手做项目。笔试前前后后刷题很重要,但刷题刷得再多,也不如把一个真实项目从头到尾做好带给你的成长多。你可以刻意在项目里使用组件化开发、状态管理、性能优化手段,自己给自己提需求。我见过很多候选人,笔试题能做高分,但问起项目经验时候说不出什么亮点,这样往往也拿不到最终的offer。两者兼顾,才能稳扎稳打。

我整理这套复盘的时候,印象最深的不是某道题怎么解,而是当时自己为了准备面试,把过去写业务代码时一直模糊的知识点一个个补上了。这种“看懂底层”的感觉,才是准备校招笔试最大的收获。希望这份复盘能帮你少走一些弯路,也能让你在准备过程中找到属于自己的学习节奏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询