十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

B站2020前端校招笔试题解析:闭包、手写代码与框架原理

B站2020前端校招笔试题解析:闭包、手写代码与框架原理 1. 题目全景B站2020校招前端笔试卷到底在考什么1.1 试卷结构与考察方向拆解做前端校招这几年我经手过不少大厂的笔试卷子B站这套2020前端笔试卷一算是比较有代表性的。整张卷子想考察的方向非常明确不再考你背了多少API而是看你在真实场景下能不能把基础原理吃透、能不能手写出工程可用的代码。从题型分布来看基本可以分成四块。第一块是基础语法与数据类型题主要围绕typeof、instanceof、闭包、原型链这些JS核心概念展开第二块是手写代码题常见的有防抖节流、深拷贝、数组去重、Promise封装第三块是CSS布局与浏览器机制题比如flex布局、BFC、事件循环、渲染原理第四块是框架原理题Vue和React双线并重尤其爱考响应式原理、setState机制、虚拟DOM diff这些。这套题最值得研究的不是题目本身而是命题逻辑。你会发现它基本都是“启动条件简单、延伸深度大”的题型一句话描述场景然后让你写实现或者让你判断输出结果。这种出题方式的优势在于不给考生“背答案”的机会每一种写法背后都能反映你真实的理解深度。比如一个简单的防抖函数应届生可能只能写出300字的初版但真正理解闭包和this指向的人会在一版里就解决掉所有边界情况。这就是大厂筛人的核心逻辑。我的建议是做这套题不要只追求“写出来”要追求“为什么这么写”后面我会逐个题拆解。1.2 命题风格背后的人才画像看完B站这套笔试题我最大的感受是它们不是在招一个“会写页面的人”而是在招一个“能自己解决不确定问题的人”。整个岗位画像的核心词就两个扎实和灵活。“扎实”体现在对基础知识的追问方式上。比如列出null undefined和null undefined的输出结果这种题表面上是判断相等实际上考验你对JS隐式转换规范的掌握程度。而typeof NaN、typeof []这类输出题更是直接检验你有没有真正理解JS的数据类型系统。这些东西不背个三五遍现场很容易在细节上翻车。“灵活”体现在手写题和场景题上。卷子里会出现类似“实现一个带最大执行次数的防抖函数”“封装一个支持取消的Promise”这类变体题拼的就是你在标准答案之外能不能拓展出工程经验。笔试阶段还好到了面试环节面试官会顺着你的代码往下追问比如“为什么这里要用apply而不是call”“如果并发请求要怎么做边界控制”。这次复盘我会把每一类题都往这个深度去讲保证你看完不只能“过笔试”还能应对后续的追问。2. 手写题核心闭包、防抖节流与深拷贝的“工程级”写法2.1 闭包与作用域一道输出题背后的执行机制B站这套笔试卷子里的闭包输出题几乎是所有面试题的“开胃菜”。比如经典的for (var i 0; i 5; i) { setTimeout(function () { console.log(i); }, 1000); }这道题输出的是5 5 5 5 5而不是0 1 2 3 4核心原因在于var声明的变量是函数级作用域循环结束后i已经变为5而setTimeout的回调是在循环结束后的宏任务阶段才执行的此时访问的i自然指向同一个变量。很多同学答错这道题不是因为不知道异步而是没有把“作用域、变量提升、事件循环”三个知识点串联起来。正确的改法也不止一种。第一是用let声明利用块级作用域为每次循环创建独立的绑定第二是用 setTimeout 的第三个参数透传局部变量第三是用立即执行函数IIFE包裹形成闭包。我推荐第三种的变体因为你能顺带回答面试官的追问“如果我偏要用var你还有哪些办法”for (var i 0; i 5; i) { (function (j) { setTimeout(function () { console.log(j); }, 1000); })(i); }这里的关键是理解IIFE的入参i在每轮循环时就被“快照”到参数j上闭包记住的是参数j而不是外部变量i。这种题没有太多花哨技巧靠的是你对作用域链和闭包原理的底层理解。2.2 防抖与节流从“能跑”到“好用”的边界处理B站笔试卷里的防抖节流题通常不只是“写出来就行”它会限定场景。我见过最典型的版本是这样的实现一个防抖函数要求首次触发时立即执行且支持取消。这个版本的考点就比普通防抖多了一层。普通防抖的核心是在事件触发后延迟执行如果在延迟期间再次触发则重新计时。标准写法如下function debounce(fn, delay) { let timer null; return function (...args) { const context this; if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); }, delay); }; }但“首次立即执行”意味着要在开头加一个判断如果timer为null立刻调用否则才启动新的定时器。而“支持取消”就需要在返回值上挂一个cancel方法清空定时器并重置状态。完整的工程版本我习惯这么写function debounce(fn, delay, immediate false) { let timer null; const debounced function (...args) { const context this; if (immediate !timer) { fn.apply(context, args); } if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); timer null; }, delay); }; debounced.cancel function () { clearTimeout(timer); timer null; }; return debounced; }节流的工程版本也同理核心是控制执行频率。需要提醒的是笔试时切记不要把防抖和节流的定义搞混防抖是“只执行最后一次”节流是“固定间隔执行一次”。面试官大概率会让你用自己的话说出两者的区别和适用场景比如搜索框输入用防抖滚动加载和按钮点击频率限制用节流。2.3 深拷贝完整方案必须考虑到这四层深拷贝是B站这类校招笔试卷里最稳的一道手写题但这道题的上限极高。普通的浅层复制写法function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); }这个版本在笔试中只能算基础分。JSON.stringify会忽略undefined、function、Symbol并且无法处理循环引用也无法处理Date、RegExp、Map、Set这类特殊对象。笔试想拿高分我的建议是至少实现到一个支持循环引用、能区分数据类型的版本。推荐一个能应对笔试和日常开发的深拷贝模板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 target Array.isArray(obj) ? [] : {}; hash.set(obj, target); Reflect.ownKeys(obj).forEach((key) { if (typeof obj[key] object obj[key] ! null) { target[key] deepClone(obj[key], hash); } else { target[key] obj[key]; } }); return target; }这里有两个细节需要展开。第一用WeakMap作为缓存目的是解决循环引用问题否则遇到a.self a会直接栈溢出。第二用Reflect.ownKeys而不是Object.keys是因为前者能拿到不可枚举属性和Symbol属性这在工程中很关键。笔试时即使不要求写到这个完整度也可以在注释里写出“这里还可以处理Symbol属性”让面试官知道你有这个意识。3. 框架与原理题从B站考法看Vue和React的核心区别3.1 Vue响应式原理从Object.defineProperty到Proxy的演进B站这套卷子里Vue相关题目主要围绕响应式原理展开。最典型的问法是“对data中的一个数组执行push操作页面会更新吗”。如果对Vue 2的响应式实现不了解很容易答错。Vue 2 的响应式重写了数组的七个方法push、pop、shift、unshift、splice、sort、reverse所以push后页面会更新但直接通过索引修改数组元素则不会触发。这就是为什么Vue 2 文档里强调要用Vue.set或splice来修改数组。Vue 3 则换成了Proxy这张卷子如果放在2020年之后考Vue 3的概率会更大。const reactive (obj) { return new Proxy(obj, { get(target, key, receiver) { // 依赖收集 track(target, key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const result Reflect.set(target, key, value, receiver); // 触发更新 trigger(target, key); return result; } }); };对比两者最大的差异在于Object.defineProperty只能劫持已有属性新增属性需要额外处理而Proxy可以对整个对象进行代理新增、删除操作都能被监听。笔试时建议从“对象是否被整体代理”“数组是否要特殊处理”“新增属性是否需要额外API”三个维度来对比回答这样逻辑清晰也方便面试官顺着往下问。3.2 React setState同步还是异步取决于谁在调用React相关题目里B站比较偏爱考察setState的执行时机。这道题的细节非常多在React 18之前setState在事件处理函数中是异步的在setTimeout、原生事件监听器、Promise回调中是同步的。到React 18之后自动批处理机制会让所有场景默认执行异步批处理同步执行的场景需要显式使用flushSync。笔试时如果遇到“点击按钮后连续调用三次setState(count count 1)最终count是多少”这类题答案是3而不是1。因为传入的是更新函数React会把多个更新合并按顺序依次执行。如果传入的是普通值比如setState(count 1)三次最终只会加1因为React的批处理机制会用最后一次赋值覆盖前面的更新。能区分这一点基本就能证明你对React状态更新机制有真实理解。4. 浏览器机制与网络基础事件循环、渲染与跨域问题4.1 事件循环与宏任务微任务输出顺序题的通解思路前端面试绕不开的必考题B站笔试卷里也有不少“写出下列代码的输出顺序”题。核心逻辑并不复杂只要你记住同步代码先执行微任务先于宏任务宏任务按先后顺序执行。来看一道典型题目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。原因在于回调队列里微任务的优先级高于宏任务而setTimeout即使延时为0也要先进入宏任务队列。动手做这类题时我习惯先在草稿纸上列出“同步任务”“微任务”“宏任务”三个队列再把每个代码块依次入队顺序就一目了然了。4.2 跨域方案不必求全但要会“对比差异”跨域是试卷里的常客B站这边也不例外。最常见的出题方式是让考生回答“如果要向另一个域名的接口发送请求有哪些方案分别有什么优劣”。我建议从以下四个主流方案讲起JSONP、CORS、代理转发、postMessage。JSONP利用script标签不受同源策略限制的原理只能支持GET请求适合老项目对接。CORS后端在响应头设置Access-Control-Allow-Origin可以支持所有HTTP方法是当前的主流方案。代理转发在开发环境用webpack的devServer.proxy生产环境用Nginx反向代理本质是让同源的服务器去请求跨域接口再返回给前端。postMessage适用于iframe和window.open打开的页面之间通信。回答时不需要每个方案都背得滚瓜烂熟但一定要能说出“为什么JSONP不支持POST”“CORS的预检请求触发条件是什么”这两个追问点。5. CSS与工程化布局、选择器优先级和前端构建5.1 布局题flex三栏布局的几种写法CSS布局题在B站这套卷子里也占一定比重尤其是flex布局。最经典的题是“左右固定宽度中间自适应”的三栏布局。答案其实不止一种.container { display: flex; } .left { width: 200px; } .right { width: 200px; } .center { flex: 1; }这里有个容易被忽略的细节flex子项的默认min-width: auto会导致内容过长时中间栏被撑破所以通常需要给中间栏加上min-width: 0或overflow: hidden。这类小坑在笔试时不容易暴露但面试官一旦让你“说一个flex布局的坑”你回答这个就非常加分。5.2 工程化题模块化规范与性能优化2020年的前端校招试卷工程化相关内容已经相当普及。B站这套卷子里出现过“CommonJS与ES Module的区别”“如何做首屏性能优化”这类题目。CommonJS和ES Module的区别建议从三个角度展开加载时机、静态分析能力、循环依赖处理。CommonJS是运行时加载模块导出的是值的拷贝ES Module是编译时输出接口模块间是值的引用。这个区别延伸出来的问题就是:ES Module能进行tree shaking而CommonJS很难做到。性能优化题这块可以从网络、渲染、代码层面各说一条。网络层做HTTP缓存、CDN、接口合并渲染层做图片懒加载、骨架屏、减少重排重绘代码层做路由懒加载、第三方库按需引入、长列表虚拟滚动。“代码分割”容易答但“为什么分包可以提升首屏速度”的回答最关键浏览器只需要下载当前路由所需的代码把非首屏代码拆出去首屏JS体积变小加载和解析自然变快。笔试时我习惯列一个“优化手段实现思路收益说明”的结构表格一目了然。6. 笔试复盘考场上的常见失分点与冲刺建议6.1 那些年我们在笔试题上踩过的坑第一类常见失分点是“手写题能跑通但不考虑边界”。防抖函数里不处理this指向、深拷贝里不考虑循环引用、节流函数里不处理最后一次触发这些都会让面试官认为你停留在“能写Hello World”的阶段。第二类是“输出题凭感觉不写推导过程”。事件循环题里如果只写答案不写步骤丢分很可惜。即使是笔试我建议也要在代码旁边简短写上“为什么”的推导因为很多在线笔试题会要求提交解释。第三类是“框架题只背结论不解释原因”。比如知道Object.defineProperty无法监听新增属性但说不清新增属性为什么不会触发响应式更新这涉及Vue初始化时递归遍历的机制也是分水岭所在。6.2 冲刺阶段的准备思路结合B站2020这套笔试题我给正在准备校招的同学三个建议。第一刷题不能只刷“简单版”。像防抖、节流、深拷贝这类题网上到处都是基础答案你要做的是主动给自己“加条件”模拟面试官会提出的实际场景限制。第二建立自己的笔记体系。每道错题不只是记录答案而是记录知识点之间的关联。比如闭包输出题关联到事件循环再关联到防抖节流和函数柯里化。这种网状结构比零散刷题有效得多。第三把“手写题”真正写到肌肉记忆的程度。考场上时间紧张手写题如果还需要现场思考思路通常写不出完整版本。建议在笔试前把高频手写题至少默写三遍一直到不需要犹豫就能写完整。从我个人经历来看B站这套笔试题的难度在当年校招里属于中上水平但它考察的知识点都非常“正”没有什么偏题怪题。认真把基础原理搞懂把经典手写题练到顺手通过笔试并没有想象中那么难。最后再分享一个小技巧在线笔试前把本地编辑器的自动补全关掉练几轮手写等上了考场你会发现真正的做题速度比你想象中要快得多。
返回列表