
又到校招季总有同学来问金融科技类公司的前端笔试到底考什么、怎么准备。今年我特意把乐信2020校园招聘前端笔试题翻出来重新做了一遍结合自己当年做题时踩过的坑以及后来帮新人改简历、做模拟面试积累的经验把这份题里涉及的考点、答题思路、常见失分点拆开讲清楚。如果你正在准备前端校招或者工作两三年想跳槽但心里没底这份复盘应该能帮你少走不少弯路。说实话乐信这份笔试题在同类公司里属于“看着不难拿高分难”的类型。它不像大厂那样上来就甩几道 hard 算法题而是把大量基础考点藏在看似普通的题目里通过追问和细节考察你有没有真正理解前端底层原理。我见过不少简历写得花团锦簇的同学在这份题上栽跟头原因不是不会而是答得太浅、太“背书”。1. 整体考情与出题思路拆解1.1 金融科技公司前端笔试想筛选什么乐信做的是金融科技业务这类公司的前端笔试从来不是单纯考你会不会写页面而是围绕“稳定、安全、性能”这三个关键词出题。金融业务对线上系统的稳定性要求极高页面白屏、接口超时、数据渲染出错都有可能直接影响用户交易所以笔试题里一定会出现事件循环、异步错误处理、数据不可变、内存泄漏这类考点。另一个隐藏考察点是责任意识。金融类产品的前端要处理大量敏感数据虽然笔试不会直接考安全攻防但会通过“跨域怎么配”“cookie 和 token 的区别”“XSS 怎么防”这类问题侧面判断你有没有安全意识。我批过几份试卷发现能把这些题答出层次感的同学通常都有实际项目经验而不只是刷过面试题。还有一点容易被忽略这类公司非常看重“能不能快速上手业务”。笔试中的框架题往往不是让你背生命周期而是结合场景问“列表数据更新后页面为什么不刷新”“组件通信有哪些方式”本质上是想看你在真实业务里如何排查问题、如何做技术选型。1.2 试题结构与时间分配策略乐信2020校招前端笔试题型的整体结构大致可以分为四块选择题/判断题、简答题、手写代码题、算法与编程题。如果我没记错总时长是90分钟题量在20道左右其中手写代码和算法占了大头。我复盘时最深的感受是时间分配比做题更重要。很多同学在前面的简答题上长篇大论写到后面的手写代码题只剩20分钟结果本来会做的题也没写完。我的建议是选择题控制在15分钟内简答题每道控制在5到8分钟剩下的时间重点留给代码题和算法题。代码题一道题的分数顶得上三道简答优先级应该最高。另外这份笔试题中有不少题目看起来是开放性的比如“说说你对前端工程化的理解”。这类题没有标准答案但阅卷人能从你的表述里看出你真实做过多少东西。如果只是背概念、堆术语反而容易暴露短板。后面我会单独讲这类题怎么答才能拿到高分。2. 核心考点逐题拆解JS基础与手写代码2.1 原型链与继承送分题里的陷阱乐信的笔试里有一道典型的原型链题大概长这样function Foo() {} Foo.prototype.name leixin; const foo new Foo(); console.log(foo.name); console.log(Foo.prototype.constructor Foo);第一问大家基本都能答出来输出leixin因为访问foo.name时实例上找不到就沿着原型链找到了Foo.prototype.name。但第二问就开始有人犹豫了。Foo.prototype.constructor指向Foo本身这没问题因为 JavaScript 默认会为每个函数的 prototype 对象添加一个 constructor 属性指回该函数。但题目真正的坑在后面如果对Foo.prototype做了重新赋值会发生什么Foo.prototype { name: leixin }; const foo2 new Foo(); console.log(foo2.name); console.log(foo2.constructor Foo);foo2.name仍然是leixin但foo2.constructor已经不再指向Foo而是指向Object。因为Foo.prototype被整体替换成了一个普通对象字面量这个对象的 constructor 默认来自它的原型链也就是Object.prototype.constructor。回答这道题时如果能主动指出这个坑并说明在封装继承时应该手动补constructor指向阅卷人会明显觉得你水平不一样。原型链这块还有一个高频考点是new的过程。我在答案里建议这样写创建一个新对象把新对象的__proto__指向构造函数的prototype将this绑定到新对象上执行构造函数如果构造函数返回了对象则返回该对象否则返回新创建的对象。关键点是把第三步和第四步写全很多人会漏掉“构造函数显式返回对象”这个特殊情况。2.2 事件循环与异步输出顺序题怎么快速推导乐信笔试有一道事件循环输出顺序题几乎是前端笔试必考题。题目类似console.log(script start); setTimeout(() { console.log(setTimeout); }, 0); Promise.resolve().then(() { console.log(promise1); }).then(() { console.log(promise2); }); console.log(script end);输出顺序是script start、script end、promise1、promise2、setTimeout。考察核心是微任务microtask和宏任务macrotask的执行时机。同步代码执行完后会先清空微任务队列再取一个宏任务执行。Promise 的.then回调属于微任务setTimeout属于宏任务所以微任务一定先输出。如果题目里加了async/await就需要特别小心。await右边的表达式会立即执行await后面的代码相当于被放进了.then里。比如async function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } async1(); console.log(script start);这里async1 start会立即打印async2()也立即执行并打印async2然后await后面的async1 end被放进微任务队列接着同步代码输出script start最后才输出async1 end。做这类题我的经验是先画一个简单的队列图把同步任务、微任务、宏任务分别列出来再按顺序执行推出基本不会错。2.3 手写防抖节流、深拷贝、Promise.all乐信的手写代码题里出现了防抖debounce和节流throttle这是前端面试的常客但我发现很多同学只能写出“能用”的版本距离“完善”还有差距。先说防抖function debounce(fn, wait) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, wait); }; }三个关键点一是返回的新函数必须是普通函数不能用箭头函数否则this会丢失二是通过fn.apply(this, args)把上下文和参数透传三是每次都清除上一次的定时器再重新设置。能答出这三点说明你真的理解防抖的原理而不是背模板。节流和防抖的区别也常被追问。防抖是“触发完等一段时间再执行”节流是“每隔一段时间执行一次”。一种常见实现是时间戳方式function throttle(fn, interval) { let last 0; return function(...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }还有一道手写深拷贝。基础版是递归遍历对象但乐信的题目里明确要求考虑循环引用问题。我在答案里用 WeakMap 记录已拷贝过的对象遇到循环引用时直接返回之前拷贝的副本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); if (hash.has(obj)) return hash.get(obj); const clone Array.isArray(obj) ? [] : {}; hash.set(obj, clone); for (let key of Reflect.ownKeys(obj)) { clone[key] deepClone(obj[key], hash); } return clone; }这里用Reflect.ownKeys是为了把 Symbol 类型的键也拷贝进来。如果只用Object.keysSymbol 键会被丢掉。很多同学不知道这个细节但踩过一次坑就记住了。Promise.all 的手写也是高频题。核心是返回一个新的 Promise遍历传入的 Promise 数组用计数器判断是否全部完成function promiseAll(promises) { return new Promise((resolve, reject) { const results []; let count 0; for (let i 0; i promises.length; i) { Promise.resolve(promises[i]).then(res { results[i] res; count; if (count promises.length) resolve(results); }, reject); } }); }题目如果继续追问“Promise.all 和 Promise.allSettled 有什么区别”你需要答出allSettled不会因为某个 Promise 失败而整体失败而是等全部结束后把每个结果的状态和值都返回。这个追问在乐信的面试环节出现过笔试阶段写对基础版本进入面试后把区别说清楚会很加分。3. 核心考点逐题拆解浏览器、网络与框架3.1 从输入URL到页面展示怎么答才不落俗套浏览器相关的简答题几乎是必考的乐信的题目是“从输入 URL 到页面渲染完成经历了哪些过程”。这类题看起来简单但大部分人的答案都停留在“DNS 解析、建立 TCP 连接、发送 HTTP 请求、服务器返回、浏览器渲染”这个层面。想拿高分需要在两个地方做差异化。第一个差异化点是把渲染过程拆细。收到 HTML 后浏览器会边解析边构建 DOM 树同时 CSS 解析构建 CSSOM 树两者合成 Render Tree然后进行布局Layout和绘制Paint。如果遇到 JavaScript会阻塞 HTML 解析所以有async和defer的优化方案。能提到“CSS 不会阻塞 DOM 解析但会阻塞渲染”这个点说明你是真的做过性能优化。第二个差异化点是把网络过程说清楚。提到 TCP 时除了“三次握手、四次挥手”还可以提一句“如果页面是 HTTPS还需要进行 TLS 握手协商密钥”提到 DNS 时可以补充“浏览器有 DNS 缓存省去部分解析时间”。这些细节不需要长篇展开点到即止阅卷人就能感受到你的深度。我当时答这道题时还额外加了一个实战案例自己在项目里用 Performance 面板分析过首屏耗时发现瓶颈在某个大图片资源的下载上后来改成懒加载后首屏时间降了快一半。这类真实案例比背一百遍原理都有用。3.2 跨域问题的解决方案对比乐信笔试里有一道跨域的简答/选择题题干大意是“前端请求接口返回跨域错误你会怎么解决”。这题本身不新鲜但考察点在于你能列出多少种方案以及是否知道每种方案的适用场景。我复盘时整理了一张对比表方案原理适用场景注意事项CORS服务端设置响应头Access-Control-Allow-Origin前后端分离的正式环境需要后端配合预检请求会多发一次 OPTIONSJSONP利用 script 标签不受同源限制回调函数注入兼容老接口、紧急临时方案只支持 GET有安全风险不推荐新项目使用反向代理浏览器请求同源地址由服务器转发到目标接口前端开发环境最常用需要维护代理配置部署时要注意线上转发规则postMessage通过 window.postMessage 跨窗口通信iframe 嵌套场景需要校验消息来源 origin我特别想说一下 CORS很多同学只知道“后端加个响应头就行”但追问“什么是预检请求”就答不上来。实际开发中非简单请求比如带自定义请求头或 Content-Type 为 application/json会先发一个 OPTIONS 预检请求服务端需要正确处理 OPTIONS 请求否则就算加了响应头浏览器照样报跨域错误。我在项目里就遇到过一回前端排了半天最后发现是后端没有在网关层放行 OPTIONS 请求。开发环境下最省事的方案其实是代理。用 Vue 的话可以在 vue.config.js 里配置 devServer 的 proxy指向后端测试环境地址用 create-react-app 的话可以配置 package.json 里的 proxy 字段。这样浏览器看到的所有请求都指向本地开发服务器就没有跨域问题了。但要注意这只是开发环境的解法上线后跨域问题必须靠服务端 CORS 或网关统一处理不能让前端去绕。3.3 框架考察Vue响应式原理与React diff乐信的笔试里框架题目不深但覆盖面广Vue 和 React 都会涉及。先看 Vue 的题目大概率会考“响应式原理”。基础答法是Object.defineProperty拦截对象的属性读取和赋值通过 getter 收集依赖通过 setter 触发更新。这里要主动补一句数组的特殊处理方式也就是重写了数组的 push、pop、shift、unshift、splice、sort、reverse 等方法。再进阶一点乐信的简答题可能会追问“Vue 3 和 Vue 2 的响应式有什么区别”。答案是 Vue 3 使用 Proxy 代理整个对象能拦截新增属性、删除属性和更多操作而 Vue 2 的Object.defineProperty无法完全监听对象新增属性所以 Vue 2 才会提供Vue.set和$set来解决这个问题。能答到这个层次说明你不仅用过 Vue还研究过原理。React 这边的题最常见的是问“为什么列表渲染时要加 key”。答案不只是“帮助 React 识别节点”关键点是 diff 算法如何利用 key。React 的 diff 算法在同层级节点比较时会通过 key 判断节点是否只是位置变了而不是整个重新创建。如果不用 key 或使用数组 index 作为 key当列表顺序变化时React 可能会错误地复用组件状态导致渲染异常或性能问题。最经典的例子就是输入框内容错位——用 index 做 key列表前插一条数据后后面的输入框状态全乱了。我记得乐信这道题给的场景是“一个待办事项列表删除中间项后页面渲染错乱”问可能是什么原因。这其实就是考 key 的正确使用能结合具体现象回答“删除后 React 原有节点状态被错误复用”的同学明显是踩过这个坑的。4. 算法题与编程题实战4.1 数组去重、扁平化的多种解法算法题里数组去重和扁平化几乎是校招标配乐信也不例外。这类题目虽然不难但阅卷人想看的不是你会一种解法而是你有没有多种解法的储备以及能不能讲清每种解法的优缺点。数组去重最常见的解法有四种。Set 一行版最简单[...new Set(arr)]但缺点是只能去重原始类型对象和 NaN 的处理还得额外注意。filter indexOf 版利用indexOf返回第一个匹配项的下标如果当前下标和第一个匹配项下标相同则保留。Map 版把数组元素作为键存起来适合需要去重的同时统计出现次数的场景。排序去重法先排序再相邻比较适合处理大数据量时可以配合双指针。我推荐优先掌握 Set 和 Map 两种解法的思路因为它们的性能通常优于 indexOf 版本。但实际答题时要看题目要求如果题目明确说“不能使用 Set”你最好还能写出手动的双循环或 filter 版本体现代码功底。数组扁平化的考点就更明显了要么考递归要么考reduce的进阶用法。基础递归版function flatten(arr) { return arr.reduce((prev, cur) { return prev.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []); }如果提升难度还会要求实现“拍平到指定深度”这就涉及到flat(depth)的原生方法原理。能答出Infinity作为深度参数说明你平时用过flat是个小加分项。4.2 一道中等难度的算法题最长连续序列乐信笔试的算法题里有一道我印象很深的题给定一个未排序的整数数组找出最长连续序列的长度。题目要求时间复杂度控制在 O(n)。比如[100, 4, 200, 1, 3, 2]最长连续序列是[1, 2, 3, 4]长度为 4。初次看到这题很多人第一反应是先排序再遍历但排序的时间复杂度至少是 O(n log n)不满足题目要求。正确解法是把数组元素都放进 Set 里然后遍历 Set只有当当前元素是某个连续序列的起点时才向内循环查找后续元素。判断起点的方式是看num - 1是否存在于 Set 中。function longestConsecutive(nums) { const set new Set(nums); let maxLen 0; for (let num of set) { if (!set.has(num - 1)) { let cur num; let len 1; while (set.has(cur 1)) { cur; len; } maxLen Math.max(maxLen, len); } } return maxLen; }我当时做这道题时第一版也写成了排序方案后来发现不满足复杂度要求才改成 Set。这个教训挺有价值的笔试里的复杂度要求往往不是摆设而是筛选“有没有算法思维”的关键。如果只是背题遇到变形就会露馅。顺带说一句这道题在 LeetCode 上的编号是 128有兴趣的同学可以去找同款练手。4.3 简答题的踩分点和答题技巧简答题虽然是手写文字但往往是决定笔试过不过的关键。乐信的简答题里有一道“如何提升前端页面性能”这类题如果只列“图片压缩、代码压缩、懒加载”这种大众答案很难和几千份答卷区分开。我的答题技巧是采用“结构化 具体数据”的方式。先按类别分点加载性能、渲染性能、运行时性能。加载性能写减少请求数、使用 CDN、资源按需加载渲染性能写减少重排重绘、避免复杂选择器运行时性能写避免内存泄漏、使用 Web Worker 处理大计算量任务。然后再补一个自己项目中的实例比如“通过懒加载路由把首屏 JS 从 800KB 减到 300KB白屏时间从 2 秒缩短到 1 秒内”。有数字支撑的答案比空谈理论可信得多。还有一道高频简答是“事件委托的原理和优势”。原理是利用事件冒泡把事件监听绑定在父元素上通过判断 event.target 来处理子元素触发的事件。优势有两个减少内存占用尤其是列表项很多时动态添加的子节点不需要重新绑定事件。如果能顺手写一个事件委托的代码片段这道题基本就稳了。阅卷人看简答题的耐心有限代码片段比长篇文字更直观。5. 常见问题与排查技巧实录5.1 我在实际做题时踩过的坑复盘这套题时我特意回顾了当年自己做题时犯过的错挑几个典型的出来聊。第一个坑是手写节流函数时漏掉了this绑定。当时我写的返回函数里直接调用fn()没做fn.apply(this, args)结果测试用例里函数里的 this 指向全局输出和预期完全对不上。这种错误在笔试环境里不容易定位因为大多数同学不会真去执行代码但阅卷人一眼就能看出来。第二个坑是没有考虑到深拷贝的循环引用。我第一版深拷贝函数遇到const obj {}; obj.self obj;这种情况会栈溢出。后来才知道要用 WeakMap 记录已经拷贝过的对象遇到时直接返回引用。这个知识点我在后面带新人时也经常强调因为实际项目里数据出现循环引用的概率不低例如组件递归引用或图结构数据。第三个坑比较隐蔽事件循环题里把await后面的代码当成同步代码算。实际上await右侧的表达式虽然是立即执行的但await之后的代码会被推迟到微任务阶段。如果题目里既有async/await又有Promise.then需要注意它们进入微任务队列的顺序通常await后面的代码相当于微任务链的下一环顺序晚于同级的.then回调。5.2 时间不够用、题写不完怎么办90分钟做完全部题目对大多数同学来说其实很紧张。我的策略是先做手写代码题和算法题因为它们分值高、踩点明确写完基本就能拿到大部分分数。简答题放在后面因为哪怕结尾不完整只要要点写到也能拿一大半分。还有一个小技巧拿到试卷先花两分钟把所有题目扫一遍。如果发现后面有一道自己特别熟的算法题可以调整顺序先做它趁状态最好时把熟练的题拿下。这就像考试时先做会做的题一样能有效减少紧张感为难题留出时间。如果某道手写代码题实在写不完至少把函数名、参数、关键思路用注释写出来。我曾经帮公司筛简历时见过有人防抖函数只写完参数定义和中止条件但批卷时会认为这个人“思路清晰但时间不够”给的分数远比空白卷高得多。不要因为写不完就放弃能写一点是一点。5.3 笔试通过后面试官在追问什么如果笔试通过面试环节大概率会追问笔试题里的一些细节。我整理了三个高频追问方向建议大家提前准备。第一个追问是防抖和节流在真实项目中用在哪里。如果你回答“搜索框输入提示、滚动加载、按钮防止重复提交”说明你真的用过。如果只背定义面试官追问一句“滚动加载用防抖还是节流”就会露馅。正确的回答是滚动加载一般用节流因为需要持续监听滚动位置并按一定频率触发搜索框建议用防抖因为用户输入完才需要请求。第二个追问是如果你写的深拷贝函数遇到函数、Map、Set 这些类型怎么办。校招同学能答到 Date 和 RegExp 已经很不错了如果能再延伸一句“生产环境建议直接用 lodash 的 cloneDeep手写版本只是为了考试和加深理解”反而显得务实。第三个追问通常是 Vue 响应式的数组下标赋值为什么不能触发更新。答案是因为 Vue 2 使用Object.defineProperty拦截的是对象属性数组的下标是索引通过索引直接修改元素时没有走 setter 的拦截逻辑。所以 Vue 2 才提供了$set方法或者建议使用数组的splice方法。这个问题如果笔试阶段答到了 Proxy 和 defineProperty 的区别面试时基本不会卡住。做完整套题的复盘我最大的感受是这套笔试题的设计思路很清晰把前端工程师日常工作中最常遇到的知识点都覆盖到了。它不需要你写出多精妙的算法但要求你真正理解 JavaScript 的运行机制、浏览器的工作原理以及主流框架的核心思想。这些东西看起来“基础”却是实际开发中每天都离不开的。如果你现在正在准备类似的校招笔试我的建议是千万别只刷题背书。每学一个知识点就想办法在项目里用一用哪怕是自己搭个小的 demo 页面也行。比如学事件循环就去写一个请求并发控制的 demo学深拷贝就试着在实际项目里接一份包含嵌套结构、可能有循环引用的接口数据。只有亲手踩过坑看到题目时才会条件反射般想到对应的细节而不是在考场上努力回忆背过的八股文。这套题我做完之后最大的收获不是记住了某道题的标准答案而是重新审视了一遍那些“我每天都在用、却从来没想过为什么”的前端基础。希望这份复盘能给你带来同样的感受。