ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

有赞校招前端笔试复盘:从基础题看JS异步与闭包的真实考察逻辑

2026/8/29 22:04:51 拓冰建站 浏览量
有赞校招前端笔试复盘:从基础题看JS异步与闭包的真实考察逻辑 有赞2019校招前端笔试第一批复盘那些基础题背后的真实考察逻辑2019年秋招投有赞前端岗的时候我还没意识到这场笔试会让我后来在面试里少踩那么多坑。有赞当时已经是电商SaaS里体量不小的玩家杭州技术团队的口碑也一直在线笔试放在牛客网上第一批90分钟。考完我把题目逐题回忆整理了一遍发现这套卷子的出题思路特别典型——不考偏题怪题但把前端基础里最容易混淆、最需要真正理解的部分挖得很深。这篇文章不是简单摆答案而是把每类题背后的考察逻辑、考场上的思考路径、以及后来我在实际业务里再次遇到这些知识点时的体会一次性说清楚。1. 笔试概况一场90分钟的线上硬仗1.1 考试形式与整体印象有赞2019校招前端笔试第一批是线上笔试牛客网平台时长90分钟。从题量设置来看单选、多选、填空题加两道编程题时间给得不算宽裕尤其是编程题需要完整跑通用例对打字速度和思路清晰度都有要求。我当时答完还剩不到5分钟几乎是压着交卷线提交的。整体难度放在2019年的校招行情里属于中上。选择题部分不是那种一眼就能看出答案的送分题很多选项之间的区别特别细微比如Promise executor里throw error和reject的区别、map和forEach在稀疏数组上的行为差异这类如果只是背过八股文而没亲手写过很容易在两个选项之间反复横跳。编程题倒没有出特别恶心的算法考的是基本功——大数相加、深拷贝这类但越是这样越容易在边界条件上丢分。1.2 题型分布与分值权重按我的回忆这套卷子大致是单选10题左右、多选5题左右、填空若干加上两道编程题。选择题覆盖的面比较广从HTML语义化、CSS布局、JS基础到浏览器渲染和网络协议都有涉及但重心明显在JS上尤其是闭包、this指向、Event Loop、原型链这些。多选是重灾区因为少选多选都不得分而且有几个选项从单一角度看都说得通必须对底层机制有完整认知才能全选对。分值上编程题占比不低每道题的分值大概相当于4到5道选择题。所以我的策略是遇到选择题拿不准的先标记跳过保证编程题有充足时间写完。这个策略后来被证明是对的——两道编程题虽然不算难但边界条件很抠比如大数相加要考虑负数、小数、超长整数的情况深拷贝要考虑循环引用和特殊对象类型仓促下笔很容易漏。1.3 有赞的考察倾向基础优先、业务强关联刷完卷子最大的感受是有赞的出题方向非常务实。作为以电商SaaS为核心业务的公司有赞前端每天面对的都是商品列表、订单状态、营销活动这种真实业务场景所以笔试题目里看似在考基础实际上处处埋着业务场景的影子。比如Event Loop的输出顺序题表面是考察宏任务微任务的执行机制实际对应的是接口请求后更新页面状态的顺序控制问题跨域的考察对应的是前后端分离下调用开放平台接口的场景。这也给后来备考的同学提了个醒刷题不是目的得想清楚每一道题在某类真实业务里落在哪里。单纯背答案应付笔试即使过了后续面试的深挖追问也会暴露短板。2. JS基础与异步校招笔试的“主战场”2.1 闭包、this指向与变量提升的连环坑有赞这套卷子里闭包相关的题出现了不止一次而且都和this指向、变量提升混在一起考。有一道题我印象很深给了一段代码问最终的输出顺序。代码大概长这样var name global; var obj { name: obj, getName: function() { return function() { return this.name; }; } }; console.log(obj.getName()());答案是global。这道题考的是闭包函数里this的丢失问题getName()执行完返回一个普通函数这个函数被直接调用时它内部的this指向window非严格模式下而不是obj。这个坑在真实业务里太常见了——回调函数里用this拿不到组件实例所以才有var self this、箭头函数、bind这些解法。有赞把这道题放在笔试里大概率是想筛选出真正在业务里写过回调、踩过this坑的候选人而不是只会做概念题的学生。变量提升的部分考得更细比如var声明提升但赋值不提升let有暂时性死区函数声明与变量声明的优先级等。我记得有一道判断题专门考typeof在变量声明前后的表现差异console.log(typeof a); var a 10; console.log(typeof b); let b 20;第一个输出undefined第二个直接报错。这道题如果只记住“var会提升、let不会提升”这种口诀是不够的——它需要你理解引擎在预处理阶段的整个工作过程var的声明会被提升到作用域顶部初始化默认为undefinedlet也会被提升但进入暂时性死区直到声明语句执行前都无法访问。这种细节层面的考察比单纯问“var和let区别”要深入得多也更能检验你是不是真的写过代码。2.2 Event Loop输出顺序题的完整推演有赞这道Event Loop题属于常规中的常规但很有代表性。代码大致是setTimeout(() { console.log(timeout); }, 0); Promise.resolve().then(() { console.log(promise1); }).then(() { console.log(promise2); }); console.log(sync);输出顺序是sync、promise1、promise2、timeout。这种题我在考前准备时刷到过无数次但真正在考场限时环境下做还是得一步步推同步代码先执行输出syncPromise的then回调属于微任务会在当前宏任务结束后立即执行setTimeout是宏任务在下一轮事件循环才轮得到。所以微任务总是先于宏任务执行。但这里有一个细节值得展开Promise.resolve().then(...)里面第一个then回调是在微任务队列里执行执行完毕后返回的是一个新的resolved Promise所以第二个then会立刻再进微任务队列。两个微任务在同一个宏任务周期内依次执行完最后才轮到setTimeout。如果这段代码里再加一层setTimeout嵌套输出顺序就会变得更有意思不过笔试没那么狠。这个知识点在实际开发中的应用场景非常直接比如页面初始化时需要先渲染数据再展示交互状态如果数据获取是异步的后续UI更新代码就应该放在同一个微任务链里而不是另起一个setTimeout否则会出现闪烁或者顺序错乱。有赞这种重度依赖异步数据流的业务类型对这个考察点的偏好不是偶然。2.3 原型链到底考到什么深度在选择题里原型链相关的题考了构造函数的prototype、实例的__proto__、instanceof的原理这三个层面。有一道题问的是function Person(name) { this.name name; } Person.prototype.sayHi function() { console.log(hi); }; var p new Person(xiaoming);问p.__proto__ Person.prototype是否正确、Person instanceof Function是否正确、p instanceof Person是否正确。这些都还好关键是一道多选里混了一个“Function instanceof Object为trueObject instanceof Function为true”的选项这道题让很多人翻车了。这两个表达式确实都是true。原型链的顶端是Object.prototype而Function.prototype本身也是一个对象Object是函数Function也是函数所以它们互相在对方的原型链上。这种绕来绕去的关系光靠死记硬背很容易记混得画图理清。有赞考到这个深度说明对基础的要求不是停留在“知道”层面而是要求“理解并且能推理”。原型链在实际业务中最常见的应用是组件继承、方法复写和类数组的处理。比如在React或者Vue项目里如果基于父类扩展一个子类组件需要理解子类实例的原型链是怎么串联到父类的prototype上的才能正确判断方法和属性的归属。这个考察点放在校招笔试里是为了筛掉那些只会用框架、不关心底层机制的候选人。3. 浏览器与网络看着基础实际决定下限3.1 HTTP缓存相关考法字段、优先级与流程有赞笔试里HTTP缓存考得不算难但很细主要围绕Cache-Control和ETag的配合使用。有一道选择题问当响应头同时包含Cache-Control: max-age3600和Expires: Wed, 21 Oct 2019 07:28:00 GMT时浏览器以哪个为准答案是Cache-Control因为它的优先级更高。这个知识点现在很多前端也说不清楚2019年的时候更是如此。缓存这块如果想答全需要把整个强缓存和协商缓存的流程理清楚。浏览器第一次请求资源时服务器返回带Cache-Control和ETag的响应头第二次请求时浏览器判断Cache-Control: max-age是否过期没过期就直接用本地缓存不发请求过期了再带上If-None-Match字段里面是ETag的值去服务器协商服务器返回304就走缓存返回200就重新下载。有赞是电商SaaS系统页面里的商品图片、JS脚本、CSS文件都依赖这套缓存机制来降低服务器压力和提升加载速度。笔试考这个实际是希望候选人入职后能读懂线上性能问题——为什么用户看到的是旧页面为什么某个资源明明改了文件名还是走缓存这些排查工作都跟HTTP缓存强相关。3.2 跨域方案CORS、JSONP、postMessage跨域这道题在2019年的前端笔试里基本是必考的有赞出的版本是让候选人判断哪些场景属于跨域以及如何解决。判断跨域的边界是个坑比如http://a.com和https://a.com端口不同也算跨域a.com和www.a.com域名不同也算跨域。方案上CORS是标准方案但需要服务器配合设置响应头JSONP只支持GET请求而且有安全隐患只能作为兼容方案postMessage主要用于iframe窗口和window.open打开的窗口之间的通信。有赞的真实场景里前端有多个子应用部署在不同域名下还会涉及和第三方小程序、H5页面的通信跨域的处理非常普遍所以这个考点其实是在为后续的微前端架构做铺垫。这里有一个答题技巧遇到跨域题除了列出几种方案一定要说出各自的适用场景和优缺点。只写“可以用CORS解决”是不完整的要说明在哪种情况下用CORS、哪种情况下不得不用JSONP比如对接不支持CORS的旧接口、哪种情况下用postMessage更合适。这种答题方式能直接体现出你是在做技术选型而不是在背概念。3.3 渲染机制与性能优化从URL输入到页面展示的完整链路有一道多选题考察了从输入URL到页面展示的过程中哪些步骤会阻塞首次渲染。选项里涉及DNS解析、TCP连接、HTML解析、CSSOM构建、JavaScript执行等。正确的理解是DNS和TCP是网络层的准备不直接阻塞渲染但HTML解析过程中遇到同步的script标签会暂停DOM解析先下载并执行脚本这就是经典的“脚本会阻塞渲染”。CSSOM的构建也会阻塞渲染因为渲染树需要DOM和CSSOM合并之后才能生成。但是如果把CSS放在link里异步加载或者把JS用defer/async修饰情况又不一样了。这道题考的是对整个渲染链路是否清晰。我当时能答对很大程度上是因为之前手动实现过一个简单的浏览器性能优化方案把首屏用到的CSS内联JS拆分并按需加载图片懒加载然后观察Lighthouse的指标变化。这种实践经历让知识点落地了而不是停留在“知道”。有赞的页面在移动端流量占比高性能优化是他们的刚需考察这个方向非常合理。4. 手写代码题考场上的“照妖镜”4.1 手写new、防抖、节流基础中的必答项编程题里有一道是手写new操作符的实现。这道题猛一看很简单但真正写起来有几个关键点容易漏。我当时的实现是function myNew(Constructor, ...args) { // 1. 创建一个新对象原型指向构造函数的prototype const obj Object.create(Constructor.prototype); // 2. 执行构造函数绑定this const result Constructor.apply(obj, args); // 3. 如果构造函数返回了对象返回这个对象否则返回新对象 return (typeof result object result ! null) || typeof result function ? result : obj; }这里的第三步最容易丢。如果构造函数内部显式返回了一个对象比如return { custom: true }那么new出来的结果应该是这个对象而不是步骤一创建的新对象。很多人在写实现的时候只做了前两步丢掉了返回值判断这就丢分了。防抖和节流也是手写题的热门。有赞考的是防抖并且要求给出立即执行版本和非立即执行版本。其实防抖的核心就是clearTimeout加setTimeout的配合function debounce(fn, wait, immediate false) { let timer null; return function(...args) { const callNow immediate !timer; clearTimeout(timer); timer setTimeout(() { timer null; if (!immediate) fn.apply(this, args); }, wait); if (callNow) fn.apply(this, args); }; }注意两条一是clearTimeout要放在判断callNow之前否则第一次触发时没有清理掉上一次的定时器虽然第一次没有上一次逻辑上还是要一致二是要用function而不是箭头函数才能让this指向调用方而不是定义时的环境。这两点写代码时很容易被忽略。4.2 超大数相加边界条件的“细节地狱”第二道编程题是JS超大数相加。题目说两个字符串形式的数字可能超出Number能安全表示的范围要求实现加法函数返回字符串结果。基础思路是模拟竖式加法从低位到高位逐位相加记录进位。function addBigNumbers(a, b) { let i a.length - 1; let j b.length - 1; let carry 0; let result ; while (i 0 || j 0 || carry 0) { const digitA i 0 ? parseInt(a[i], 10) : 0; const digitB j 0 ? parseInt(b[j], 10) : 0; const sum digitA digitB carry; result (sum % 10) result; carry Math.floor(sum / 10); i--; j--; } return result; }这段代码的核心在于循环条件的设置。while里的carry 0是最后一道保险——如果最高位相加之后还产生了进位比如999 1最高位计算完后carry是1这个1也必须写入结果。我当时第一版只写了i 0 || j 0测999 1的时候就漏了最前面的1幸好测试用例暴露了问题赶紧补上。考场上时间紧张这种细节最容易翻车。如果题目再加一层难度让处理负数和浮点数边界条件会更多。我当时猜到了可能有负数用例但因为时间关系没有实现完整版只加了提示说“如果传入负数当前实现不适用”。这种做法在笔试里可以接受但如果面试官追问负数怎么处理得能答上来先判断符号再比较绝对值大小决定是否借位核心还是模拟竖式。4.3 深拷贝循环引用与特殊对象的处理有赞笔试题里的深拷贝常规版本用递归就能做到但完全写对不容易。function deepClone(target, map new WeakMap()) { if (target null || typeof target ! object) return target; if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target.source, target.flags); if (map.has(target)) return map.get(target); const clone Array.isArray(target) ? [] : {}; map.set(target, clone); Reflect.ownKeys(target).forEach(key { clone[key] deepClone(target[key], map); }); return clone; }这个版本处理了三类问题一是Date和RegExp这样的特殊对象直接走普通递归会变成空对象二是循环引用比如const a {}; a.self a;如果没有WeakMap记录已克隆对象递归会无限循环导致栈溢出三是Symbol作为键的情况用Reflect.ownKeys而不是Object.keys才能遍历到。考场上我没有写完整版只写了递归加循环引用的处理Date和RegExp的判断没写上后来复盘才发现丢分点在这里。后来在业务里遇到深拷贝我直接用structuredClone这个原生API它已经完全兼容深拷贝的处理了但在2019年还要自己实现。5. 框架与工程化了解深浅的分水岭5.1 React与Vue的考察深度有赞实际用的是哪条技术路线有赞2019年前端主要用的是React技术栈但笔试里并没有直接限定框架而是通过一些偏原理性的选择题来考察候选人是否理解框架的核心机制。其中有一道题考的是React中setState后组件什么时候重新渲染选项里涉及了shouldComponentUpdate、PureComponent、useMemo、useCallback这些。这道题其实是在考察对React渲染流程的理解setState是异步批处理的多个setState会合并为一个更新但如果放在Promise回调里React 18之前的版本又不会自动批处理。2019年的时候React还处于16.x时代对于考试来说知道setState是异步的、合并的并且shouldComponentUpdate返回false可以阻止重渲染基本就能答对。Vue那边虽然没有直接考但有一道题问到了“数据响应式”选项里提到了Object.defineProperty和Proxy的区别。这其实是在考察候选人对Vue 2和Vue 3差异的理解。虽然当时Vue 3还没有正式发布但了解Proxy可以监听对象属性的新增和删除而Object.defineProperty做不到已经是加分项。这个考察方向对准备者来说是一个提示不要只学一个框架至少要理解其他框架的核心思路并且能说出彼此的优缺点。尤其在有赞这种技术栈以React为主的公司如果候选人只在简历上写了Vue笔试里体现出的原理深度会直接影响筛选结果。5.2 组件通信类问题业务场景下的实际选型组件通信是框架题里和实际业务最贴近的。有赞这套卷子里有一道多选题描述了一个实际场景列表页点击某一条数据详情页需要知道用户点的哪一条。选项给了props传递、中央事件总线、Redux/Vuex、Context/PROVIDE_INJECT等方案。正确的判断是最简单的场景用props逐层传递就够了但一旦层级很深或者多个兄弟组件需要共享同一份数据就应该用全局状态管理而不是把所有组件都强行props串联起来。2019年的时候React生态里Redux还是主流但Context API也已经可以用了Vue生态里就是Vuex。这个场景在有赞的后台管理系统里非常常见商品列表、订单列表、营销活动列表所有的列表页到详情页的跳转都涉及数据传递的问题。而且有赞的SaaS系统是面向商家端的后台功能模块多、组件嵌套深如果不选好通信方案维护成本非常高。笔试把这种业务场景包装进框架题里目的就是考察候选人是不是真的做过大型项目而不是只写过todo list。5.3 工程化我印象中的webpack与构建优化工程化部分的题目不多但有一道关于webpack的题很典型。它给了一段webpack.config.js的配置里面有entry、output、resolve.alias、plugins等配置项让判断哪个配置是错误的。这道题本身不难难点在于alias里配置路径别名时用了相对路径还是绝对路径、有没有配置符号以及output里的publicPath设置是否合理这些细节很容易混淆。我印象比较深的是还有一道问tree-shaking生效条件的题。答案是只有ES Module的静态import语法能被正确分析CommonJS的require无法静态分析所以不能tree-shaking。当时我正好在本地demo里验证过这个特性所以答得比较快。如果只是背过“tree-shaking可以摇掉没用到的代码”未必能选对。有赞作为电商SaaS前端项目一般都比较庞大构建速度和产物体积直接影响CI/CD效率。笔试考察webpack不是为了让候选人记住配置项而是希望候选人入职后能独立排查构建问题、优化打包策略。所以备考的时候我建议真实地搭一个webpack项目把常见优化手段都手动配置一遍光看文档效果很差。6. 考后复盘一次笔试暴露出的真实水平6.1 我的时间分配失误选择填空花太久编程题差点没写完考完之后我第一件事就是复盘时间分配。说实话我前面20分钟在选择题上耗了太久特别是多选每道题都反复斟酌结果做到编程题的时候只剩35分钟。第一道大数相加还算顺利第二道深拷贝就有点仓促了循环引用的问题是写完以后才想起来补上导致代码里出现了一处逻辑判断的重复虽然能跑通测试用例但可读性明显下降了。如果再来一次我会给自己定一个硬性规则选择题每题最多1分钟拿不准的先标一个最可能的答案然后果断跳过或者用排除法选一个把时间留给编程题。因为选择题的分值再高一道也就一两分编程题动辄10分起步。这个策略在后来的其他笔试中帮我拿到了不少offer算是一次踩坑换来的经验。6.2 值得反复咀嚼的题目细节多选的“少选多选都不给分”有赞这套卷子里多选占了不少分值而且提示很清楚少选、多选都不得分。这其实比很多笔试要严格有些平台是多选少选按比例给分的全对才稳。这个规则带来的直接影响是不确定的选项不要去赌。我当时的策略是只选我有十足把握的不确定的选项宁可漏掉也不要因为多选一个而全盘皆输。这个策略在后面几道涉及Event Loop和原型的多选题里救了命。比如有一道题其中一个选项描述的是“微任务中嵌套微任务会插入当前队列尾部”我当时对这个表述没有完全把握最终没有选它。后来查资料确认这个表述是对的但我依然认为在“少选不扣分多选扣全分”的规则下保守是对的。笔试不追求完美追求的是总分最大化。6.3 给后来者的备考建议把基础题练到“肌肉记忆”如果时间倒流让我重新准备有赞这套笔试我会把重心放在几个方向上也分享给准备类似校招笔试的同学第一JS基础不能只靠背要亲手列一遍输出结果并解释原因。闭包、this、Event Loop、原型链、Promise这些给自己出几道变式题比如把function改成箭头函数把var改成let看输出是否变化。这种刻意练习能让理解真正深入。第二网络基础和浏览器渲染要画图理解不能只看文章。《图解HTTP》加上自己画一张从URL输入到页面展示的流程图把每一步阻塞点标出来比刷十篇文章都管用。第三手写代码题一定要在纸上或者无IDE提示的编辑器里练。牛客网笔试没有智能提示完全靠记忆写API比如Reflect.ownKeys这个写法如果平时IDE自动补全用惯了考场上很可能卡壳。我在考前专门用VS Code的纯文本模式练手写效果不错。第四多花时间研究目标公司的业务场景把基础题往业务上靠。有赞做电商SaaS所有工程题都可能包一层业务外衣。理解了这个逻辑做题的时候就知道出题人想考什么答题时也能有的放矢地带上“这个知识点在XX场景下是这样用的”。第五也是最重要的一点平时写代码时多问一个“为什么”。为什么setState要设计成异步的为什么浏览器会限制并发请求数为什么ES Module能被静态分析而CommonJS不行这些问题在笔试里不会直接考但它们背后的知识网络会以各种形式出现在选择题和编程题的边界条件里。真心建议新手不要只刷题去折腾一些小项目把每个报错都搞明白笔试自然就稳了。