
2018年秋招那会儿我正好在准备前端方向的校招字节跳动的笔试印象特别深。第二批次的题和第一批不太一样但考察的核心思路是一致的不看你背了多少API而是看你对JS这门语言的理解深度、对浏览器运行机制的认识程度以及工程化实践里踩过多少坑。现在回过头来看这套题很多考点依然是面试高频区甚至可以说当年这些题目划定了一个前端工程师基本功的考察范围。这篇文章把第二批次涉及的核心问题、解题思路和面试延伸展开聊聊给正在准备大厂前端面试的同学做个参考。1. 当年那场笔试整体结构与考察风向1.1 2018年前端技术栈的“时代背景”为什么这决定了题目风格2018年是前端工程化大爆发但又没完全尘埃落定的一年。Vue 2.x已经非常流行React 16也有了Fiber架构TypeScript开始进入主流视野但还没完全统治Webpack 4刚发布不久HTTP/2在国内大厂逐步落地但还没全面普及。当时的前端校招笔试题不会像现在让你直接写TS类型体操也不会考你微前端怎么设计因为那时候连微前端这个概念都还没在工程里普遍应用。那会考察的重心很明确ES6核心语法promise、箭头函数、解构、class、模块化、浏览器事件循环机制、HTTP缓存策略、闭包与作用域链、原型链与继承这些基础中的基础。这套题之所以到现在还有参考价值是因为它构建了一个“基础能力”的考察坐标系——不管框架怎么变、工具链怎么演进JS语言本身的特性、浏览器的工作机制、网络协议的基本原理一直是前端工程师能不能走远的分水岭。回头看第二批次这套题整体题型大致分两类一类是选择题覆盖语法细节、输出顺序、作用域陷阱这类题特别考验对语言特性的精确掌握另一类是编程题要求手写实现一些常见功能。选择题的坑挖得很细不是那种一眼能看出答案的题而是需要你在脑子里把代码执行过程完整走一遍才能答对。1.2 题型分布与考察模块占比从总体来看第二批次题目的考察模块分布大概可以归纳为下表考察模块典型题型大概占比JS语言核心闭包、this、原型链、作用域选择题、代码输出题30%异步编程事件循环、Promise、setTimeout选择题、代码输出题25%浏览器与网络缓存、渲染机制、跨域选择题、简答题20%框架与工程化Vue/React基础、Webpack选择题15%手写编程题防抖、深拷贝、数组去重、事件总线编程题10%这个分布很有意思。语言核心和异步编程加在一起超过了一半说明字节当年的思路就是先把语言功底筛出来再谈别的。框架和工程化的占比反而不高因为校招生入职后会有很长的学习期框架可以现学但JS基础和异步编程的理解深度一旦欠缺后面补起来非常吃力。这个考察逻辑放到现在的面试里依然是大厂的主流思路。2. 笔试重点题型拆解每一道题背后的考点是什么2.1 JS语言核心类题目闭包、this、原型链的经典陷阱第二批笔试的JS语言核心类题目出题风格非常典型——给一段代码让你判断输出结果或选择描述正确的选项。这类题表面上看是考语法实际上考的是“你对JavaScript执行机制的理解”。举个例子闭包类题目经常会这样出for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }答案是输出5个5。如果你不理解var没有块级作用域、以及闭包捕获的是变量引用而不是值这道题就会答错。而如果你知道改成let声明或者用IIFE立即执行函数包一层就能正确输出0到4并说出背后的原理这道题才算真正吃透。this指向问题同样是一个高发区。经典的考察方式是var obj { name: byte, getName: function() { return this.name; } }; var fn obj.getName; console.log(fn());这里的坑在于当你把obj.getName赋值给一个变量再调用时函数内部的this就不再指向obj了而是指向全局对象。究其原因this的指向取决于函数调用方式而不是定义位置。这种题在面试中还会以多种变体出现比如嵌套对象里的方法调用、箭头函数和普通函数的this对比。再说原型链的考察。2018年那会很爱考下面这类题function Person(name) { this.name name; } Person.prototype.sayHello function() { console.log(Hello, this.name); }; var p new Person(ByteDancer); p.sayHello(); console.log(p instanceof Person); console.log(p.constructor Person);这道题表面很简单但面试官往往会在笔试之后的面谈环节继续追问new操作符到底做了什么事如果我把Person.prototype.sayHello改成箭头函数会怎样这其实已经把考察从“知不知道”拉到了“能不能说清楚底层机制”的层面。建议你在准备时一定要能把new的过程用代码描述出来——创建一个新对象、把新对象的__proto__指向构造函数的prototype、将构造函数的this绑定到新对象上、如果构造函数没有显式返回对象则返回这个新对象——这四步缺一不可。2.2 异步编程类题目事件循环与Promise的“输出题”套路异步编程是第二批笔试的重头戏也是很多同学失分最严重的地方。考的不是你能不能写出Promise而是给你一段混合了setTimeout、Promise、async/await的代码让你判断输出顺序。这类题的底层逻辑基于JavaScript的事件循环机制同步代码先执行然后是微任务队列最后是宏任务队列。但2018年的考题里有几个容易踩坑的细节第一Promise.resolve().then(...)注册的回调属于微任务会在当前宏任务结束后立即执行而setTimeout注册的回调属于宏任务要等到下一轮事件循环。所以“先微任务后宏任务”这个规则能帮你快速排除错误答案。第二async/await本质上是Promise的语法糖await后面的代码会作为微任务继续执行。如果要判断输出顺序可以先把async函数转换成Promise链的写法转换对了输出顺序基本就对了。举个例子这类题的经典形态是这样的async function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); async1(); new Promise(function(resolve) { console.log(promise1); resolve(); }).then(function() { console.log(promise2); }); console.log(script end);正确答案是script start - async1 start - async2 - promise1 - script end - async1 end - promise2 - setTimeout。如果你能一次答对说明对事件循环的层级关系已经掌握得比较扎实。如果答错了建议把整个流程手动画一遍不要用画图工具就用纸笔一步一步记录。为什么因为手画的过程能逼着你把“当前执行的是哪个任务任务执行完之后队列里还有什么”这个状态理清楚。这个能力不仅应对笔试有用排线上问题的时候同样重要。2.3 浏览器与网络类题目缓存、跨域与渲染机制浏览器相关的题目在第二批次里占比不低最常见的两个方向是HTTP缓存和跨域。HTTP缓存这部分考察的核心是强缓存和协商缓存的区别。选择题经常这么出如果服务端返回的响应头里包含Cache-Control: max-age3600浏览器在接下来的一个小时内再次请求该资源会怎样答案是直接从本地缓存读取不再发起网络请求。但如果响应头同时带有ETag缓存过期后浏览器会带上If-None-Match头字段去服务端做验证服务端返回304时浏览器继续使用本地缓存这属于协商缓存。你要真的吃透缓存体系不能只记字段还要理解浏览器为什么设计两套缓存机制。强缓存的价值是免请求适合长期不变的静态资源比如打包后的JS文件名带hash内容变了文件名就变天然适合强缓存。协商缓存的每一次验证还需要一次网络请求适合无法确定多长时间内不变的资源。这两种机制组合起来才能既保证更新及时又最大化利用缓存。跨域问题也是高频考点。2018年那会儿的核心解法是JSONP和CORS现在的话还会考到代理转发、postMessage等方式但原理是相通的浏览器的同源策略限制的是页面脚本从一个源去请求另一个源的资源CORS本质上是通过服务端返回Access-Control-Allow-Origin响应头来告知浏览器允许跨域。理解这个原理之后后面的配置就水到渠成。2.4 手写编程题那些“简单但容易出错”的实现编程题方面第二批次的题目大多围绕数组和函数展开。常见的有数组去重、防抖节流、深拷贝、数组扁平化、事件总线。这些题看起来简单但真要写得无懈可击还是有很多细节需要考虑。以深拷贝为例如果你只写一个递归遍历对象属性的版本在笔试中能拿到一些分但距离满分还有差距。一个完备的深拷贝实现需要处理循环引用用一个WeakMap缓存已拷贝的对象、数组类型、Date和RegExp等特殊对象、Symbol属性。如果还想做得更完善可以处理函数的拷贝策略通常直接复用引用以及通过原型链继承的属性。在当前的前端面试中这道题的优化空间依然很大值得花时间把不同版本都写一遍。防抖和节流也是高频手写题。防抖的意思是事件触发后等待一段时间再执行如果在这段时间内再次触发则重新等待节流是限制一段时间内最多执行一次。这两个工具函数的使用场景完全不同——搜索框建议是防抖因为用户输入完才去请求滚动加载建议是节流因为不想每个滚动帧都去执行计算。写实现的时候要特别注意this绑定和参数的透传因为实际项目里防抖节流往往用在事件处理函数上this指向不能丢。3. 面试延伸从笔试题目到深挖原理的层层追问3.1 手写Promise与实现一个简单的Vue响应式系统笔试过了之后面试环节的深挖几乎必然会从笔试题延伸出来。字节面试官非常擅长从一个简单的考点发散到一连串相关问题你答到了哪个层面他就顺着那个层面继续往下问。拿Promise来说笔试可能只让你判断输出顺序但面试可能直接让你手写一个符合Promise/A规范的Promise。手写Promise是个很好的分水岭——它能检验你对状态机、回调队列、then链式调用、错误处理这些核心机制的理解。我当时写的时候花了不少时间处理then的回调注册顺序和状态已经改变后的执行逻辑写完之后对Promise的执行时序有了豁然开朗的感觉。Vue响应式系统的实现也是一个经典深挖点。2018年在字节面试中出现频率很高毕竟字节当时内部也大量使用Vue。面试官往往从“Vue 2.x的响应式原理是什么”开始让你讲Object.defineProperty如何拦截属性读取和赋值然后追问数组的响应式为什么需要单独处理再延伸到为什么Vue 3要改用Proxy。这一连串追问的底层逻辑其实是考察你有没有真正理解“依赖收集”和“触发更新”这八个字。你会不会实现不重要重要的是能不能用自己的语言把这个过程讲清楚。3.2 Webpack构建流程与Loader、Plugin的区别工程化方面第二批次笔试中出现过Webpack相关的基础选择题比如loader和plugin的区别、常用的loader有哪些。这类题到了面试环节会变成更开放的问题给你一个场景让你说说Webpack从入口文件开始经历了哪些阶段最终产出bundle。如果你理解Webpack的整体流程这道题其实可以分几个层次回答先解析配置找到入口文件然后从入口出发递归解析模块依赖构建依赖图在解析过程中遇到不同类型的文件交给对应的loader处理比如.vue文件交给vue-loader.css文件交给css-loader和style-loader然后经过插件系统在特定生命周期钩子中做优化和资源处理最后输出bundle。Loader和Plugin的区别也在这个流程中体现得很清楚loader负责转换文件内容Plugin负责在构建流程的特定节点做更复杂的操作。这里有一个很容易被忽视的细节loader的执行顺序是从右到左的。如果你写use: [style-loader, css-loader]Webpack会先执行css-loader把CSS文件解析成JS模块再执行style-loader把样式注入到页面。方向搞反了构建出来的页面样式就会出现问题。这类工程化的实操经验光背书是不行的得自己搭个项目跑一遍构建过程才能有深刻记忆。3.3 项目深挖面试官真正想从你的项目里看到什么字节的面试还有一个显著特征非常看重项目经验的真实性。面试官会对你简历上写的项目做“地毯式”追问从技术选型到遇到的具体问题再到解决方案的思考过程每个环节都会细问。这背后的逻辑其实很简单项目经历能反映出一个人的技术判断力和解决复杂问题的能力。如果你在项目里做过性能优化他会问你怎么定位性能瓶颈、怎么量化优化的收益如果你做过前端工程化工具他会问你为什么选Webpack而不选Rollup、遇到构建速度慢的问题是怎么处理的如果你做过组件库他会问你怎么设计API才能兼顾灵活性和易用性。这类追问最忌讳的就是背答案。最有效的应对方式是在面试前把你做的项目从“整体架构”到“关键实现细节”完整梳理一遍尤其要想清楚你做的每个技术决策是出于什么考虑有没有考虑过其他方案最终为什么选了当前这个如果你能回答出这些“为什么”项目深挖这一关基本就稳了。4. 备战复盘我踩过的坑和改进后的准备节奏4.1 时间规划与刷题策略现在回头看我当时准备校招时犯过两个比较大的错误。第一个是前期过于追求刷题数量而忽略了总结同样的题型换了个数字重新出现我还是会错说明没有从题目中提炼出共性的解题方法。第二个是笔试前没有限时模拟导致真到了笔试的时候时间分配出现问题——前面的选择题花的时间过多后面的编程题来不及写完整。改进之后的节奏可以这样安排提前三个月开始第一阶段以系统复习为主把JS高级程序设计红宝书里的原型链、闭包、异步、DOM、事件等章节过一遍配合MDN文档补充细节第二阶段转向刷题以牛客网的前端题库和力扣的JavaScript相关题目为主每天固定时间刷一定数量的选择题和编程题重点记录错题第三阶段进入模拟冲刺严格按照笔试时间来刷整卷并练习手写代码的速度和准确性。有一个习惯我觉得特别值得推荐每做错一道题不要急着看答案先把题目复制下来在旁边写清楚“我当时为什么这么想”和“正确答案的逻辑是什么”。等复习后期再翻这些错题记录你会发现自己的思维漏洞非常集中往往就是那么几个点反复出错这比盲目刷新题要高效得多。4.2 高频失分点与避坑建议从第二批次和其他大厂笔试题来看有几个失分点相当集中值得单独拎出来提示一下。第一个是变量提升和暂时性死区。var声明的变量会被提升到作用域顶部初始值为undefined而let和const声明的变量也存在提升但在声明之前访问会进入暂时性死区直接报错。很多同学知道“let不会变量提升”这个结论但不知道为什么遇到具体代码时容易混淆。这个知识点在笔试选择题中出现的频率很高建议把提升和暂时性死区这两个概念彻底搞清楚包括它们在函数作用域和块级作用域下的不同表现。第二个是数组方法是否改变原数组。map、filter、reduce不会改变原数组splice、push、pop、sort、reverse会改变原数组。听起来很简单但一旦和引用类型的浅拷贝混在一起错误率就会飙升。比如对一个二维数组执行slice()得到的“新数组”里层元素仍然是原数组里对象的引用修改里层对象会影响到原数组。这种细节在笔试中经常被用来出陷阱题。第三个是CSS的层叠上下文与BFC。虽然第二批次笔试中CSS占比不高但面试环节很容易被问出来。两列布局怎么实现、position的几种值有什么区别、float脱离文档流之后父元素高度塌陷怎么解决这些经典问题至今仍是面试题库的常客。4.3 学习路线的取舍广度优先还是深度优先准备校招那段时间我一直在纠结一个问题是先把Vue、React这些框架的文档都过一遍还是集中精力把JS基础打牢。当时的实际经验告诉我对大厂校招来说深度优先是更正确的策略。原因很简单校招面试官默认你是一个可塑性很强但经验尚浅的候选人他考察的重心不是你会用多少框架API而是你有没有能力在未来的工作中快速学习和成长。JS基础、事件循环、浏览器原理、网络协议这些底层知识是衡量你“学习能力”最可靠的参考指标。框架层面的知识只要基础扎实上手新框架往往只需要一两周。反过来如果基础不牢就算背了一堆框架API面试官通过几个深挖问题就能看出你的底子。所以我的建议是把70%的精力花在JS核心、浏览器和网络上剩下30%分给框架和工程化。5. 这套题放到现在还有参考价值吗5.1 历久弥新的核心考点客观来说2018年的这套笔试题放到2026年的今天其中一大部分考点依然是前端面试的必考内容。JS的执行机制、事件循环、闭包、原型链、HTTP缓存、跨域、深拷贝和防抖节流的手写实现这些题目在今天的大厂面试中依然高频出现。为什么这些考点能一直保持生命力因为它们触及的是一个前端工程师赖以生存的“底层能力圈”。不管前端框架怎么迭代最终都要运行在JavaScript引擎和浏览器提供的运行时环境里不管工具链怎么演进网络请求和资源加载的基本规则没有本质变化。这部分知识更新速度慢但价值密度高属于“一年不学不会过期学一次收益多年”的类型。5.2 已经发生变化的考察方向当然前端面试的考察方向也在变化2018年和2026年有一些明显的差异。现在的面试更注重TypeScript的类型设计与泛型推导这在2018年基本不是校招考察重点。微前端架构、性能监控体系、前端工程化平台的建设在新题库中的出现频率明显提升。AI辅助编程工具的出现也让面试官更关注候选人如何高效使用工具同时考察你是否具备在AI辅助下独立判断代码质量的能力。但这并不意味着2018年的题就过时了。它更像是一面镜子照出了前端面试“变与不变”的部分。把不变的部分掌握扎实再跟进变化的部分准备校招的路线就清晰很多。5.3 从一套笔试看面试官的选人逻辑最后说说我从这套题里读出的选人逻辑。字节这类大厂校招面试官真正想选的人不是“背了最多题解的人”而是“能在未知问题面前展示出清晰思考过程的人”。面试官问你一个不会的问题他想看的不是“你承认不会”而是你会怎么去推测、怎么去拆解、怎么去逼近问题的核心。这个能力很难通过短期刷题获得需要在平时学习新知识时有意识地训练。有个小方法可以分享平时读技术文章或源码的时候不要只记结论试着多问自己几个“为什么这样设计”“如果不这样设计会怎样”“有没有其他实现方案”。这种思维习惯一旦养成面试时被问到没准备过的问题你也能现场推导出一个合理的思路。这一点才是2018年这套题真正想筛选出来的东西。