2020年那阵子校招前端笔试,乐信这套题算是在朋友圈流传比较广的一套。原因很简单:它不像有些大厂笔试那样上来就是一堆偏题怪题,而是把前端开发日常真正会碰到的知识点,用选择题、简答题和手写题的形式串了一遍,整体风格务实,考察面覆盖了JavaScript基础、浏览器原理、框架应用、工程化和算法五个方向。我当时把整套题目完整做了一遍,又对着参考答案逐题核对了细节,今天把整套题的拆解和复盘整理出来,希望能给准备校招前端岗、或者想自查基础是否扎实的朋友一些参考。
这套题适合谁看?如果你正在准备前端面试,或者刚入行不久想系统梳理前端知识体系,再或者你是带新人的前端组长,想找一套质量不错的笔试题目来摸底,都可以参考这篇复盘。我会按题型板块逐个拆解,手写题会给出完整实现思路,并解释每一步为什么这么写。
1. 笔试整体结构与考察思路
1.1 这套卷子到底在考什么
乐信2020校招前端笔试题的题型大致可以分成四类:单选题、多选题、简答题、编程题。从题目风格来看,出题人明显不是想刁难人,而是想筛选出“基础扎实、代码习惯好、有工程思维”的候选人。
具体考察方向我梳理了一下,基本围绕这几个模块:
- JavaScript 核心机制:原型链、this 指向、闭包、事件循环、异步编程,这部分占比最高,也是校招笔试题的绝对主力。
- 浏览器与网络基础:HTTP 缓存、跨域方案、浏览器渲染机制、前端安全,这些决定了你能不能写出线上不会出问题的代码。
- 框架应用能力:Vue 响应式原理、虚拟 DOM、生命周期、组件通信,乐信的技术栈以 Vue 为主,所以框架题基本围绕 Vue 展开。
- 工程化与性能优化:模块化规范、构建工具、首屏优化、懒加载,这部分考察你是否了解代码从编写到上线整个过程。
- 算法与数据结构:数组去重、排序、动态规划这类基础题,难度不会超过 LeetCode 简单到中等,但要求手写且考虑边界条件。
有个很重要的信号是:这套题里手写题占了很大比重,而且不是让你写一个函数名就完事,而是要求完整实现、考虑边界、必要时还要写注释。这说明公司希望招进来的人能直接上手写业务代码,而不是只会背概念。
1.2 题型分布与答题节奏建议
整套题标准的笔试时间是 90 分钟到 120 分钟,如果你在真实的笔试环境里,节奏控制非常重要。我给当时自己定的策略是:选择题和简答题控制在 30 到 40 分钟内完成,剩下所有时间全部留给编程题,因为手写题才是拉开差距的地方。
选择题里有个特点:不少题目是“二选一”的纠结题,比如两个选项看起来都对,但一个是充分条件一个是必要条件。这种题不要恋战,先标记好做后面的,回头再纠结。简答题则要注意书写结构,分点作答比一团文字好得多,面试官阅卷时一目了然。
还有一个容易被忽略的点:笔试系统如果支持本地 IDE 调试,一定不要直接裸写。先把思路注释写清楚,再逐步填充实现,这样即使实现没写完,至少思路分能拿到。我见过不少候选人,函数名写对了,但里边的逻辑一塌糊涂,这种比“思路对但没写完”得分还要低。
2. JavaScript 核心考点逐题拆解
2.1 原型链与继承的两道题
原型链是前端面试八股文里最经典也最绕的一块,乐信这套题里出现了两道跟原型链直接相关的题目。一道是选择题,给了三段代码让你判断输出结果;另一道是简答题,让写原型链继承的完整实现。
选择题大概长这样:
function Parent() { this.name = 'parent'; } Parent.prototype.getName = function() { return this.name; }; function Child() { this.name = 'child'; } Child.prototype = new Parent(); var child = new Child(); console.log(child.getName()); console.log(child instanceof Parent); console.log(child.constructor === Parent);逐行分析:child实例化时,Child构造函数内部把this.name设置成了'child',所以child.getName()沿原型链找到Parent.prototype上的方法,执行时this指向child,返回'child'。child instanceof Parent为true是因为Child.prototype被赋值成了 Parent 的实例,原型链上确实能找到Parent.prototype。第三个结果实际上是true,原因在于Child.prototype被整体覆盖后,child.constructor指向的是Parent,这是个经典的坑。
这道题暴露出来的问题在于:很多人能答对第一问,但第二问和第三问含糊不清。核心要理解instanceof检查的是原型的继承链,而不是构造函数来源。如果你在答这类题时能把“原型查找路径”画出来或者用文字描述出来,面试官对你好感度会直线上升。
简答题让写一个“寄生组合式继承”,这是目前最推荐的继承实现方式,因为只调用一次 Parent 构造函数,并且避免了在 Child.prototype 上生成多余属性。核心代码是用Object.create来创建父类原型的副本:
function inherit(Child, Parent) { Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; }这里有个容易被忽略的细节:一定要重新设置constructor,否则实例的constructor会错误地指向Parent,后续做类型判断时会踩坑。
2.2 this 指向与闭包陷阱
this 指向是每套前端笔试题必考的内容,乐信这套题里出现了一道非常典型的输出题:
var name = 'window'; var obj = { name: 'obj', getName: function() { return function() { return this.name; }; } }; console.log(obj.getName()());答案是'window'。原因很简单:obj.getName()返回的是一个普通匿名函数,这个函数在执行时没有任何调用者,非严格模式下 this 指向全局对象。如果题目改成var fn = obj.getName; fn();结果也会是'window',因为函数整体被抽出来调用后,它内部指向就丢掉了调用者上下文。
这套题里还考了一个高频闭包陷阱:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 0); }输出结果是 5 个 5,原因是var声明的变量 i 属于函数级作用域,for 循环结束后 i 已经变成 5,而所有回调函数共享同一个 i。如果改成let声明,每次循环都会生成一个块级作用域,输出 0 到 4。这道题还有一层变体:如果要求必须用 var,你怎么实现正确输出?经典方案是用立即执行函数包裹:
for (var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(j); }, 0); })(i); }我在实际笔试里会在答案旁顺手写一句“因为函数参数是按值传递,所以每次传入的 i 都被拷贝到了新的执行上下文里”,这样面试官能看出你是真的理解而不是背答案。
2.3 事件循环与异步输出顺序
这道题算是整套笔试题里区分度最高的题目之一。给出的代码是这样的:
console.log('start'); setTimeout(() => { console.log('timeout'); }, 0); Promise.resolve().then(() => { console.log('promise'); }); console.log('end');正确输出顺序是start、end、promise、timeout。核心逻辑是:同步代码先执行完毕,然后 JavaScript 引擎会先检查微任务队列,把 Promise 的回调执行完,之后才会执行宏任务队列里的 setTimeout 回调。
这里的重点不是背结论,而是要理解事件循环的完整流程。很多人在微任务里再嵌套微任务就会绕晕,比如这道变体:
Promise.resolve().then(() => { console.log('p1'); }).then(() => { console.log('p2'); }); setTimeout(() => { console.log('t1'); }, 0)输出是p1、p2、t1,因为 p1 执行后产生的 p2 依然属于微任务队列,会在当前宏任务结束前全部清空,不会把时机让给宏任务。这个机制是理解 async/await 行为的基础。
我在实际做题时的经验是:把代码分成“同步区”“微任务队列”“宏任务队列”三列,按时间顺序往里填,这样十拿九稳。这套题里还有个细节是async/await的题目,本质上也是微任务的变体,只要原理吃透,怎么变形都不怕。
2.4 手写防抖、节流与深拷贝
手写防抖节流几乎成了前端笔试的保留项目,乐信这套题里两个都考了,还额外考了深拷贝,这三道题放在一起,考察的就是候选人写业务工具函数的基本功。
防抖和节流的区别,我用一句生活化的话总结:防抖是“电梯等人”,有人进电梯就重新计时,等没人进才关门;节流是“定时班车”,不管后面多少人排队,固定时间才发一趟车。代码实现上,防抖的核心是setTimeout和clearTimeout的组合,保证最后一次触发在延迟后执行:
function debounce(fn, delay) { let timer = null; return function() { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments); }, delay); }; }看到没有,这个this和arguments的透传非常关键。如果不传,在对象方法里 debounce 包装出来的函数内部 this 就会丢失,实际业务中会出 bug。
节流实现则有两种思路:时间戳版和定时器版。时间戳版是立即执行,定时器版是延迟执行。我会选择合并写,保证既能保首帧又能保尾帧:
function throttle(fn, interval) { let last = 0; return function() { const now = Date.now(); if (now - last >= interval) { last = now; fn.apply(this, arguments); } }; }深拷贝那题更有意思。题目明确要求“考虑循环引用”,这直接卡掉了大部分人写的那种JSON.parse(JSON.stringify(obj))一行流。我给出的实现是这样:
function deepClone(target, map = new WeakMap()) { if (typeof target !== 'object' || target === null) { return target; } if (map.has(target)) { return map.get(target); } const result = Array.isArray(target) ? [] : {}; map.set(target, result); for (const key in target) { if (target.hasOwnProperty(key)) { result[key] = deepClone(target[key], map); } } return result; }用WeakMap而不是Map的原因是:当原对象不再被引用时,WeakMap 里的键值对可以被垃圾回收,防止内存泄漏。这是校招级别很少人能写出来的细节,如果你在笔试里写出这个,绝对是加分项。完整版本还需要处理 Date、RegExp、Map、Set,但核心的循环引用环节只要过了,这道题就拿下了。
3. 浏览器与网络基础实操要点
3.1 HTTP 缓存方案分析
这套题里有一道简答题,让你说出浏览器 HTTP 缓存的完整流程。这种题在实际开发中极其重要,因为前端发布后经常遇到用户缓存不更新的问题,本质就是没搞懂强缓存和协商缓存的关系。
我的答题框架是分两层:强缓存和协商缓存。强缓存阶段,浏览器判断 Cache-Control 里的 max-age 或者 Expires,如果没过期,直接使用本地缓存,不发任何请求;如果过期了,进入协商缓存阶段,带着 If-Modified-Since 或 If-None-Match 去请求服务器,服务器如果判断资源没变化,返回 304,浏览器继续用缓存,如果变化了,返回 200 和新的资源。
这里有个核心坑点,也是面试官真正想考的:Cache-Control 的优先级高于 Expires,而 ETag 的优先级高于 Last-Modified。为什么?因为 Last-Modified 只能精确到秒,同一秒内文件被修改两次就判断不出来;而 ETag 是文件内容的指纹,只要内容变了,指纹一定变。
我在业务里常用的一个组合是这样的:
Cache-Control: no-cache ETag: "特定的hash值"no-cache的意思是“每次使用缓存前必须先向服务器验证”,而不是“不缓存”。它配合 ETag 能兼顾性能和实时性,比较适合活动页这类内容经常变动的场景。纯静态资源则用max-age=31536000, immutable,配合文件名 hash 来做永久缓存。
3.2 跨域场景与解决方案
跨域题目在乐信这套题里出现的形式是一道场景题:页面前端调用一个第三方接口,浏览器报跨域错误,问怎么解决。这道题看起来简单,但能真正答好的人不多,因为它涉及前后端两侧的知识。
最常用的方案有三个。CORS 是正统的方案,后端在响应头加上Access-Control-Allow-Origin,注意如果请求带有自定义 Header 或者使用非简单请求方法,还需要处理 preflight 预检请求。JSONP 是利用 script 标签没有跨域限制的特性,但只支持 GET,而且需要后端配合返回特定格式的 JS 代码,现在用的场景越来越少了。开发环境下最常用的是 webpack devServer 的 proxy 代理,请求先发到本地开发服务器,再由它转发到目标服务器,绕开了浏览器的同源策略。
我在笔试里答这道题会额外提一句:上线后如果前端和后端域名不同,通常会用 Nginx 做反向代理来转发/api路径的请求,而不是在前端代码里写死跨域地址。这样好处是灵活,后端地址怎么变都只改一层 Nginx 配置。这个细节能看出你有真实的上线经验,而不是只会在本地调接口。
3.3 渲染机制与性能优化
浏览器渲染机制那题,考察的是从输入 URL 到页面展示的完整过程,虽然这是老生常谈,但乐信这套题的加分点在于它要求“结合前端性能优化来分析”。这就不是死记硬背能答好的了。
完整流程我通常这样描述:先 DNS 解析域名得到 IP 地址,然后建立 TCP 连接(现在基本都是 HTTPS,还要加上 TLS 握手),发送 HTTP 请求拿到 HTML 文档;浏览器解析 HTML 构建 DOM 树,同时解析 CSS 构建 CSSOM 树;两者合成渲染树,经过布局、绘制、合成,最终展示在屏幕上。要注意 JavaScript 在解析过程中默认会阻塞 DOM 构建,所以 script 标签的defer和async属性就是在解决这个阻塞问题。
能加到性能优化里的内容才是拉开分数的关键:首屏白屏时间长的原因是渲染进程需要先等 HTML 解析完才能发下级的 CSS 和 JS 请求,所以解决办法是内联关键 CSS,或者用preload提前加载关键资源;图片体积大是另一个常见的瓶颈,所以要使用 WebP 格式、懒加载加上合适的响应式图片尺寸。答题时如果你能提到 LCP、FCP 这几个 Core Web Vitals 指标,面试官会认为你真的做过性能监控。
4. 框架题与工程化实战
4.1 Vue 响应式原理与被考烂的 nextTick
乐信这套题的框架部分明显是 Vue 技术栈为导向,而且考得比较深。最典型的一道题是:Vue 2 的响应式原理是如何实现的?数据更新后 DOM 为什么不是立即更新?
Vue 2 的响应式核心是基于Object.defineProperty的 getter 和 setter 拦截。每个组件实例创建时,Vue 会递归遍历 data 的所有属性,把它们转换成 getter/setter 形式。当组件渲染时,会读取这些属性触发 getter,从而把当前渲染的 watcher 收集到依赖列表里;当属性被修改时触发 setter,通知所有依赖该属性的 watcher 重新执行,也就是派发更新。
这道题的隐藏考点是:由于Object.defineProperty只能拦截属性的访问和赋值,对于新增属性和删除属性是无能为力的,所以 Vue 2 才提供了Vue.set和Vue.delete来解决这个问题。如果你能在答案里提到这一点,并且说明Vue 3用 Proxy 从根本上解决了这个问题,那这道题就答得相当完整了。
nextTick 那题也很有代表性。Vue 在修改 data 后,DOM 并不会立即更新,而是把同一个事件循环里的所有数据变更合并起来,在下一个微任务里统一更新组件。这样做的目的是减少不必要的 DOM 操作,提升性能。nextTick 就是把回调延迟到下次 DOM 更新循环结束后执行的 API。实际开发中经常遇到修改数据后立刻用this.$refs拿 DOM 高度,结果拿到旧值的情况,解决办法就是在 nextTick 回调里读取。
4.2 虚拟 DOM 与 Diff 过程
这套题里关于虚拟 DOM 的题目是一个简答题:为什么 Vue 需要虚拟 DOM?虚拟 DOM 一定比真实 DOM 快吗?
第一问比较好答,虚拟 DOM 的核心价值不是“快”,而是“让跨平台成为可能”。因为虚拟 DOM 是一个普通 JS 对象,它可以运行在浏览器、Node 端、小程序等各种环境里,只要在对应平台实现 render 逻辑,就能复用同一套组件逻辑。另一个价值是免去手动操作真实 DOM 的繁琐,开发者只需要描述数据状态,框架负责把状态映射成界面。
第二问是个好陷阱。虚拟 DOM 不一定更快,首次渲染时真实 DOM 操作反而更快,因为虚拟 DOM 需要先创建虚拟节点树再做 diff。虚拟 DOM 的优势在于后续更新时,它能用最小代价找到需要变更的节点,减少不必要的 DOM 操作。另外在多平台渲染的场景下,虚拟 DOM 的价值不是性能,而是统一了编程模型。
关于 Diff 算法,重点要说明 Vue 是同层对比,不会跨层级移动节点;对比过程中用了头头、头尾、尾头、尾尾四个指针来快速判断,处理列表时通过 key 来复用节点。这里有一个非常经典的考点:为什么不建议用 index 作为 key?因为当数组发生头部插入时,index 全部错位,Diff 认为所有节点都变了,列表会被整体重新渲染,性能反而更差。而且如果节点内部有状态,比如 input 输入框,整个状态还会错乱。如果你笔试时间充裕,我会建议把这个场景写出来作为例子说明。
4.3 模块化与构建优化
工程化题目在这套题里占的比重不算大,但有一道题值得拿出来复盘:如何做前端项目的首屏加载优化?答题时容易只想到压缩和懒加载,但完整方案需要从整个加载链路上系统思考。
首先在构建层面,代码压缩、tree-shaking、按路由拆分代码块并配合动态 import 实现懒加载,这些是最基本的手段。其次在应用层,对于 SSR 或预渲染之外的普通 SPA,要避免主包过大,入口文件只引入核心依赖和路由配置,不相关的组件全部按需加载。第三是静态资源层面,图片用懒加载和现代格式,把小的图片转成 base64 或者内联,减少 HTTP 请求数。
我当时还在答案里加了这样一个细节:使用 dll 或 manualChunks 把第三方库单独拆包,利用浏览器缓存特性,让用户二次访问时只加载业务代码的增量,而不是把所有代码重新下载一遍。这个思路在如今 Vite 时代依然有参考价值,虽然配置方式变了,但底层逻辑一致。
模块化那题考的是 CommonJS 和 ES Module 的区别。需要答出几个关键点:CommonJS 是动态的、运行时加载,输出的是一个值的拷贝;ES Module 是静态的、编译时加载,输出的是值的引用。ES Module 之所以能做 tree-shaking,正是因为它的静态性让构建工具在编译阶段就能分析出哪些导出没有被使用。
5. 算法与手写题现场实录
5.1 数组去重与排序,不要只写一行
数组去重是看起来最简单,但实际能完整答好的题。我见过太多人一行Array.from(new Set(arr))交差,应对笔试可以,但面试官追问一句“如果数组里有对象怎么办”就卡住了。
乐信这套题里数组去重也考了,但题目要求“至少写出两种方案,并说明各自适用场景”。这就是给候选人机会炫技的题,我最后分了三个方案:Set 适合基本类型、filter 加 indexOf 适合普通场景、对象键值或 Map 适合需要考虑类型和对象的场景。如果数组元素是对象,用 Set 完全无效,因为每个对象都是不同的引用,真正的方案是用属性特征去重:
function uniqueObjects(arr, key) { const seen = new Map(); return arr.filter(item => { if (seen.has(item[key])) { return false; } seen.set(item[key], true); return true; }); }数组排序那题,题目要求手写快排,考察递归和分治思想。我在笔试里实现的标准版本是:
function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[Math.floor(arr.length / 2)]; const left = []; const right = []; const middle = []; for (let item of arr) { if (item < pivot) left.push(item); else if (item > pivot) right.push(item); else middle.push(item); } return [...quickSort(left), ...middle, ...quickSort(right)]; }注意本题里的细节:把相等元素单独放到 middle 数组,而不是丢到 left 或 right,否则相等元素会导致递归死循环。我实际笔试时还额外补充了快排的平均复杂度和最坏复杂度,并说明为什么最坏情况是 O(n²),这种“顺便把原理讲透”的做法对拿高分很有用。
5.2 数组扁平化与最大子序和
扁平化题目属于多解型选手,考察的其实是 JavaScript 语言基本功。一道题让实现flatten([1, [2, [3, [4]]]]),要求结果为[1, 2, 3, 4],我用了递归加 reduce 的实现:
function flatten(arr) { return arr.reduce((acc, cur) => { return acc.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []); }这一版足够通过,但笔试时我还补了通配方案:讲清楚Array.prototype.flat的默认展开一层,传入 Infinity 可以无限展开,以及 toString 和 split 组合的方式只适用于纯数字数组,遇到字符串数组就会出错。这几个补充点展示了你对 JS 边界情况的敏感度。
最大子序和是 LeetCode 经典题,也是这套笔试题里唯一的动态规划题。题目是:给定一个整数数组,找出一个具有最大和的连续子数组,返回其最大和。核心是定义状态dp[i]表示以第 i 个元素结尾的连续子数组的最大和,状态转移方程是:
dp[i] = max(nums[i], dp[i - 1] + nums[i])最终答案是所有 dp[i] 中的最大值。笔试时我还顺便写了滚动数组优化,把空间复杂度从 O(n) 压到 O(1)。这道题拿满分的关键不是写出来,而是把状态转移方程写出来、用文字说明为什么这样设计,再给实现。这正是校招笔试和社招面试里算法题的考察方式:不只看你能不能 AC,还看你能不能讲清楚。
5.3 LRU 缓存实现
LRU 是面试手写题里的常客,乐信这套题也考了一道。题目:设计一个 LRU 缓存类,支持 get 和 put 操作,容量有限,当缓存满时淘汰最久未使用的数据。要求 get 和 put 的时间复杂度均为 O(1)。
在 JavaScript 里最优雅的实现方式是基于 Map 的有序特性。Map 会记住键值对的插入顺序,所以每次访问某个 key 时,先删除再重新插入,就能让它排到最末尾,这样最久没用的数据自然就排在 Map 的最前面,需要淘汰时直接删除第一个元素:
class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); } get(key) { if (!this.cache.has(key)) return -1; const value = this.cache.get(key); this.cache.delete(key); this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } this.cache.set(key, value); if (this.cache.size > this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } } }这个实现已经足够通过笔试,我自己在真实做题时还会补一句:容量判断一定要放在 set 之后,否则当 key 已存在且容量已满时,你会在 set 新值之前误删另一个 key。这种边界问题是笔试题里最容易翻车的地方,多花一分钟思考边角情况,往往能挡住大多数竞争对手。
6. 常见问题与避坑指南
6.1 笔试现场最容易被扣分的点
刷过大量笔试题之后,我总结出校招笔试最典型的扣分点,回头看乐信这套题也基本都踩过。
第一个是只写答案不写过程。手写题要求实现某个功能,你直接写一行最高级 API 的调用,就算结果对,面试官也看不到你的思路和编码习惯。校招批卷时,手写题看的从来不只是最终结果,而是步骤和表达能力。正确做法是先写思路注释,再写步骤拆分,再补实现。
第二个是不注意题目细节。比如要求用 ES5 实现,你通篇用了箭头函数和 const,直接扣掉大半分。再比如要求在特定位置加注释,你写了但注释内容没有解释清楚。我在实际笔试中碰到过要求“写出三种实现方案并说明适用场景”,结果很多人只写了一种,不是不会,是没读完题。养成通读全题的习惯很关键。
第三个是变量命名太随意。a、b、fn、temp这类命名虽然不影响运行,但面试官看到的第一反应就是代码习惯不好。用debouncedFn、cacheMap、visitedSet这样的具名变量,能给人留下“这个候选人会写生产级代码”的印象。这属于低成本高收益的加分项。
6.2 针对校招与跳槽季的复习建议
如果你正在准备前端笔试,我给几条实际有用的建议,而不是空泛的“多刷题”。
首先,按模块建一个自己的面试笔记。不要直接抄别人的面经,而是把每一道题用自己的语言和代码再写一遍。比如这套题里的防抖节流和深拷贝,你今天看完觉得懂了,过两天不写一遍肯定手生。跟健身一样,脑子记住了不代表手记住了。
其次,算法题要有策略地刷。前端笔试的算法题,高频主要集中在数组、字符串、链表、栈、队列、简单的动态规划和哈希表。LeetCode 上的困难题在校招笔试里出现的概率不高,性价比太低。优先刷简单和中等,且每道题保证能写出暴力解和优化解两种方案。
第三,简历上列的项目一定要能被深挖。我这次做乐信这套笔试题时注意到,框架和工程化题目虽然占比不小,但真正的考察重心还是在基础是否能灵活运用。建议准备项目时,把项目里用到的每一个技术点都往原理方向准备一层:为什么用这个、底层怎么实现的、有什么替代方案、性能问题怎么排查。比如项目里用了懒加载,你就要能说出路由懒加载的原理是什么,import() 返回的是一个 Promise,构建工具会把它单独打成一个 chunk 文件。
关于复习节奏,我的建议是提前两个月开始,第一个月系统过基础,第二个月专项刷手写和算法。每天保持两道代码题的量,一套基础概念题的量,周末拿一套完整卷子模拟笔试环境,手机静音,严格计时。这套练习方式坚持下去,到真正笔试的时候心态会稳很多。
6.3 笔试送分技巧:先写注释再写代码
最后分享一个我实测很管用的技巧:笔试题里所有手写代码,先在答题区域用注释把实现步骤列出来,再在注释下面填代码。比如让你实现一个debounce,你可以先写:
// 1. 定义 timer 变量保存定时器 // 2. 返回一个包装函数 // 3. 包装函数里清除上次定时器 // 4. 开启新定时器,延迟执行回调 // 5. 透传 this 和 arguments function debounce(fn, delay) { let timer = null; return function() { clearTimeout(timer); const context = this; const args = arguments; timer = setTimeout(function() { fn.apply(context, args); }, delay); }; }这样做有两个好处:一是避免写着写着思路断掉,注释就是你的提纲;二是万一时间不够没写完,面试官也能从注释里看到你的完整思路,这部分照样能给分。我在真实笔试中几次靠这个技巧,在代码没完全跑通的情况下依然拿到了大部分分数。
乐信这套2020年的校招前端笔试题,放到现在来看,考点依然没有过时,甚至可以说非常接近真实业务对前端工程师的基本要求。前端面试准备没有太多捷径,把基础模块一个个吃透,再通过手写题把抽象概念转化成肌肉记忆,踏踏实实积累,后面不管是笔试还是面试都会顺畅很多。