详解字节前端笔试题:闭包、原型链与事件循环等核心考点全解析 📅 发布时间:2026/8/29 22:13:08 👁 浏览次数: 每年到校招季就有很多同学翻出往年大厂的笔试题来刷。字节跳动2017年前端工程师实习生笔试题在网上流传很广我当年也认真做过一遍。说实话这套题放在今天看部分考点已经过时比如对CSS Hack的考察、对jQuery的依赖但更多东西依然是前端面试的常客甚至可以说是“基础中的基础”闭包、原型链、事件循环、HTTP缓存、手写数组去重和深拷贝。如果你正在准备前端实习或者校招这套题值得花时间拆解一遍关键是搞清楚每一道题背后的出题意图和考点边界而不是背答案。这篇文章我就以这套笔试题为线索把前端高频考点逐个拆开讲清楚。我会结合当年的实际答题情况补充一些考场上容易踩的坑再告诉你哪些解法在今天的面试里依然能打哪些已经被淘汰了。内容覆盖JavaScript核心机制、CSS布局、算法手写题、浏览器与网络基础四个大块基本对应笔试里最常见的题型结构。1. 笔试题的整体布局与出题逻辑1.1 字节当年笔试喜欢考什么2017年的前端实习生笔试时间一般是90到120分钟题量大概在15到20道之间题型以选择题、简答题和手写代码题为主。字节的题目有一个明显特点不玩偏题怪题考的都是日常开发里会用到的能力但会在细节上挖坑。比如闭包和循环变量的问题几乎每年必考因为这就是业务代码里最常见的写法陷阱。从整体布局来看这套笔试大致可以分成四个方向JavaScript语言基础约占40%。包括数据类型判断、作用域与闭包、this指向、原型链、异步与事件循环、数组字符串常用方法。CSS与页面布局约占20%。包括盒模型、定位与浮动、flex布局、垂直水平居中方案、移动端适配。浏览器与网络基础约占20%。包括HTTP缓存机制、跨域方案、浏览器渲染流程、本地存储。算法与手写代码约占20%。包括数组去重、深拷贝、防抖节流、二分查找、字符串处理等。这个比例在今天的面试里依然有参考价值。JS基础和手写代码的比重甚至更高了很多一面二面直接就是白板写题。1.2 为什么字节特别看重基础有人觉得大厂面试更看重框架和项目经验其实正好相反。实习生的项目经验本来就有限面试官没法通过项目判断你的技术深度那怎么筛选只能靠基础题。基础题能反映几件事第一你的计算机基本功扎不扎实。比如事件循环能不能讲清楚原型链能不能边说边画这比背了多少API更能说明问题。第二你的代码习惯好不好。手写数组去重有人一上来就写Set一行流有人会先考虑传入参数是否为数组、是否包含引用类型、需不需要保持原始类型。这两种答案的含金量完全不同。第三你的排查能力怎么样。笔试里很多题表面上问“输出什么”实际上是在模拟一段线上代码出了问题考你能不能快速定位原因。所以准备这类笔试别去背“标准答案”要把每个知识点当成一个可以展开讲三分钟的话题来准备。你不仅要写出结果还要能解释为什么有这个结果换个写法结果会不会变。2. JavaScript核心考点深度拆解2.1 闭包与循环变量陷阱这套题里有一道出现频率极高的题循环里用setTimeout输出i问控制台打印什么。类似下面这样for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); }答案是连续输出5次5而不是0、1、2、3、4。原因在于var声明的i是函数级作用域循环结束后i的值已经是5。setTimeout里的回调函数在执行时会通过作用域链找到同一个i此时i已经变成了5。面试官接着就会问怎么改成输出0到4有三种常见方案你需要都掌握。第一种使用let声明变量。let是块级作用域每次循环都会创建一个新的绑定所以回调函数拿到的是当前循环的ifor (let i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); }第二种使用IIFE立即执行函数把每次循环的i作为参数传进函数在函数内部形成一个新的作用域for (var i 0; i 5; i) { (function(j) { setTimeout(function() { console.log(j); }, 1000); })(i); }第三种在setTimeout的第三个参数上做文章for (var i 0; i 5; i) { setTimeout(function(j) { console.log(j); }, 1000, i); }第三种方案知道的人少一些但也是一个合法的解法核心是利用setTimeout第三个及以后的参数会传递给回调函数这个特性。面试时能写上来的话会是加分项说明你对API的细节有了解。这个考点为什么重要因为它在业务代码里非常常见——循环里绑定事件、循环里发起异步请求、循环里创建定时器这些场景在真实开发中每天都在发生。如果你不理解闭包捕获的是变量引用而不是值线上就会出现一堆“数据对不上”的诡异bug。2.2 this指向问题核心理解this指向的题在笔试里也是常客。给你一段代码问某一行里的this指向什么。首先记住最核心的判断方法函数在调用时this指向调用它的那个对象如果没有调用者则指向全局对象严格模式下是undefined。举个例子var name global; var obj { name: obj, getName: function() { return this.name; } }; var fn obj.getName; console.log(fn()); console.log(obj.getName());第一行fn()输出global。虽然fn原本是obj里的方法但把方法赋值给一个变量后调用调用者变成了全局对象this自然就丢了。第二行输出obj因为通过obj.getName()调用时调用者就是obj。箭头函数在这点上完全不同。箭头函数本身没有this它会捕获定义时所在作用域的this值而且这个绑定不会被改变var name global; var obj { name: obj, getName: function() { var inner () { return this.name; }; return inner(); } }; console.log(obj.getName());这里最后输出的是obj。因为箭头函数inner定义在getName函数内部getName的this指向obj所以inner捕获了obj的this。这个特性在使用React类组件、VueOptionsAPI时非常常用回调函数里要保持外层this用箭头函数是最省心的方式。还有个经典组合题把函数、闭包、this放在一起。比如setTimeout里调用对象的方法this会变成global。解决方案通常是先在外部var that this缓存一下或者直接用箭头函数。这类题的考察逻辑都是一样的核心是你是否真正理解了this的绑定规则。2.3 原型链与继承的实现原型链几乎是必考题字节的笔试题里通常不会直接问你“什么叫原型链”而是给你两个构造函数让你判断某个对象是否具有某个属性或方法。理解原型链最简单的方式是画一条查找链。访问一个对象属性时先找自身属性找不到就沿着__proto__找构造函数的prototype再找不到就继续往上找直到Object.prototype再往上就是null。function Animal(name) { this.name name; } Animal.prototype.say function() { console.log(this.name); }; function Dog(name, breed) { Animal.call(this, name); this.breed breed; } Dog.prototype Object.create(Animal.prototype); Dog.prototype.constructor Dog; var dog new Dog(旺财, 柴犬); dog.say();这里涉及到几个关键点Animal.call(this, name) 实现了属性的继承子类实例拥有了自己的name属性。Object.create(Animal.prototype) 构建了原型链让Dog.prototype能沿着Animal.prototype继续查找方法。手动修正Dog.prototype.constructor否则构造函数指向会变得混乱。原型链继承这道题面试官还可能顺着问instanceof的原理。instanceof是通过遍历左侧对象的原型链看能否找到右侧构造函数的prototype。所以上面的dog instanceof Animal是truedog instanceof Dog也是true。ES6之后有了class语法很多人以为用class就不需要理解原型链了。其实class只是语法糖本质还是原型链。面试如果问“class和function构造有什么区别”结论是class不能直接调用、有暂时性死区、方法不可枚举但最终走还是同一个原型机制。2.4 事件循环与异步输出顺序事件循环的题几乎每个大厂都会考。此类题的经典套路是这样的console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); Promise.resolve() .then(function() { console.log(promise1); }) .then(function() { console.log(promise2); }); console.log(script end);输出顺序是script start、script end、promise1、promise2、setTimeout。很多人不理解为什么Promise的回调会先于setTimeout执行。因为它们属于不同的队列setTimeout回调进入宏任务队列Promise.then回调进入微任务队列。宏任务执行完一个浏览器会清空当前所有微任务之后才会取下一个宏任务。所以微任务的优先级是高于宏任务的。对事件循环的完整理解可以总结为执行全局脚本代码这是一个宏任务。遇到同步代码就立即执行。遇到Promise.then、MutationObserver、queueMicrotask等把回调放入微任务队列。遇到setTimeout、setInterval、I/O操作、UI渲染等把回调放入宏任务队列。当前宏任务执行完毕后清空微任务队列。执行下一个宏任务循环往复。把这个机制背下来还不够最好顺手实现一个简易的调度器这能体现你对异步底层的理解。高级一点的面试官会继续问Node.js里的事件循环和浏览器端的差异比如process.nextTick在微任务队列里的位置、setImmediate和setTimeout的执行顺序如果你能答上来就属于意外加分。2.5 深浅拷贝与数组去重的多种实现手写代码题里数组去重和深拷贝是出镜率最高的两道。数组去重第一直觉写Setfunction unique(arr) { return [...new Set(arr)]; }这样写当然没问题但面试官会追问如果数组里有对象怎么去重如果数组里有NaNSet能不能正确处理如果要兼容老浏览器你用不用indexOf如果你能自己主动提到这些边界情况分数立刻不一样。一个相对完整的去重函数至少要考虑三种情况function unique(arr) { var result []; var seen new Map(); for (var i 0; i arr.length; i) { var item arr[i]; if (item ! null typeof item object) { var key JSON.stringify(item); if (!seen.has(key)) { seen.set(key, true); result.push(item); } } else { if (!result.includes(item)) { result.push(item); } } } return result; }当然这个版本用JSON.stringify做key会在严格等值上有限制但在笔试题里已经能体现思考的完整性。面试官要看的不是你写出了完美无缺的代码而是你能不能识别这个问题的复杂度在哪。深拷贝也类似。刚学前端的人会写JSON.parse(JSON.stringify(obj))但这个方法有很多坑会丢掉undefined、函数、Symbol会把Date变成字符串遇到循环引用会直接报错。一个能拿分的深拷贝函数要处理基本类型、普通对象、数组、Date、RegExp还要用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); var clone Array.isArray(obj) ? [] : {}; hash.set(obj, clone); for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] deepClone(obj[key], hash); } } return clone; }这里用WeakMap记录已经拷贝过的对象遇到循环引用时直接返回已经拷贝好的副本不会陷入死循环。这个解法在面试里属于中上水平值得熟练掌握。3. CSS与页面布局考点分析3.1 盒模型一定是第一个被问的CSS的题在笔试题里通常以选择题和简答题出现。盒模型是第一个绕不开的点几乎所有公司都会考。标准盒模型width和height设置的是content的尺寸padding和border是额外叠加的。IE怪异盒模型width和height包含了content、padding、border也就是说你设的宽度就是元素渲染后的总宽度。CSS里用box-sizing切换content-box是标准盒模型border-box是IE盒模型。实际开发中全局设置border-box是主流做法因为这样设置padding和border不会撑破布局栅格系统算宽度时也更省心。和盒模型绑定的还有margin塌陷问题两个相邻元素的margin会取最大值而不是相加父元素和子元素的margin-top也会发生塌陷。解决方式包括给父元素加overflow:hidden、加padding、加border、或者用flex/grid直接规避。3.2 垂直水平居中的方案清单居中问题在笔试题里属于必考。我见过最全的考察方式是“请写出不少于三种垂直水平居中的方案并说明每种适用场景”。这道题答好了很加分因为能体现你的方案选型能力。第一种flex布局最推荐.parent { display: flex; justify-content: center; align-items: center; }第二种绝对定位加transform对子元素尺寸没有强制要求.parent { position: relative; } .child { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }第三种绝对定位加margin:auto.parent { position: relative; } .child { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; }这种方法要求子元素有确定的宽度和高度但代码本身很简洁。第四种使用grid.parent { display: grid; place-items: center; }四种方案适用场景不同flex是最通用的transform方案不会影响布局流margin:auto方案在绝对定位容器里很可靠grid适合现代浏览器的整体布局。面试官还可能追加一个问题“如果子元素尺寸不固定哪种方案最容易出问题”答案是绝对定位加负margin的方案因为需要写死尺寸。这就提醒你做题别只记代码要想清楚每种方案的前提条件。3.3 BFC到底解决什么问题BFC块级格式化上下文是CSS高频理论题。笔试题通常这样问什么是BFC哪些方式可以创建BFCBFC解决了什么问题简单来说BFC是一个独立的渲染区域内部元素的布局不会影响外部元素。创建BFC的常见方式overflow不为visible比如hidden、auto、scroll。display设为inline-block、flex、grid、table-cell。position设为absolute或fixed。float不为none。BFC解决的三大问题是清除浮动带来的父元素高度塌陷、阻止margin外边距塌陷、防止元素被浮动元素覆盖。一个经典的清浮动写法.parent { overflow: hidden; }原理就是给父元素创建了一个BFC让它的内部子元素无论是否浮动都被包裹在这个独立的渲染区域里这样父元素的高度就能包含浮动子元素了。这块内容不算难但值得把每个触发条件和解决场景都过一遍因为在简答题里经常要求写出示例。3.4 flex布局常用属性的面试考察flex布局在2017年已经是主流现在更是页面布局的标配。笔试题一般考两类一类是给定一段flex样式判断元素最终排列位置另一类是自己写flex实现某个布局效果。需要熟练掌握的属性可以归为三类容器上的属性包括flex-direction控制主轴方向、justify-content控制主轴上的对齐、align-items控制交叉轴上的对齐、flex-wrap控制换行、gap控制间距。项目上的属性包括flex-grow放大比例、flex-shrink缩小比例、flex-basis初始主轴尺寸。flex缩写记忆法flex: 1 等价于flex: 1 1 0%意思是放大量为1缩少量为1初始尺寸为0。flex: none等价于0 0 auto即不伸缩。还有一个高频题是“实现圣杯布局或双飞翼布局”当年的笔试题会要求你用flex或者float实现左右固定宽度、中间自适应的三栏布局。用flex的写法非常简洁.container { display: flex; } .left { width: 200px; } .right { width: 200px; } .center { flex: 1; }这个布局思路在后台管理系统的整体框架中仍然常见。4. 算法与手写代码的应对套路4.1 高频算法题的解题模板字节的笔试算法题一般不考太难的动态规划更多是考察基础数据结构和编程习惯。这里有几个高频题先把模板思路理清楚。二分查找几乎年年考。写的时候最重要的不是思路而是边界条件。这里给一个不容易出错的左闭右闭写法function binarySearch(arr, target) { var left 0; var right arr.length - 1; while (left right) { var mid Math.floor((left right) / 2); if (arr[mid] target) return mid; if (arr[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }关键点在于while条件是left rightmid用Math.floor向下取整。只要坚持一套固定模板就不会出现死循环或越界的问题。斐波那契数列也是一个经典题。递归写法最简单function fib(n) { if (n 1) return n; return fib(n - 1) fib(n - 2); }但递归写法时间复杂度是O(2^n)n到40就开始卡。所以面试时更好的答案是动态规划版的迭代写法用两个变量滚动记录前两项的值function fib(n) { if (n 1) return n; var prev 0; var curr 1; for (var i 2; i n; i) { var temp curr; curr prev curr; prev temp; } return curr; }这题考的是“你能不能看出递归的重复计算问题并给出优化方案”而不是单纯让你写出递归。如果你能主动提到底层用数组缓存或者用前两项滚动更新面试官就会觉得你有算法优化意识。字符串反转、回文判断也是送分题但要注意空间复杂度。原地反转数组用双指针。4.2 手写防抖与节流防抖和节流在前端面试里的地位极高因为它直接对应真实业务场景。笔试题喜欢给一个场景题用户连续点击提交按钮需要保证最后一次点击后才发送请求请实现。防抖的核心思路是事件触发后设置一个定时器在设定时间内再次触发则重置定时器。代码如下function debounce(fn, delay) { var timer null; return function() { var context this; var args arguments; clearTimeout(timer); timer setTimeout(function() { fn.apply(context, args); }, delay); }; }注意this和参数的保留不能用箭头函数一把梭否则调用方的this就丢了。这是手写题里一个隐蔽的扣分点。节流和防抖的区别是节流保证在一个时间段内至少执行一次适合滚动事件里做懒加载。实现方式有两种时间戳版本和定时器版本最好两个都会写function throttle(fn, interval) { var last 0; return function() { var context this; var args arguments; var now Date.now(); if (now - last interval) { last now; fn.apply(context, args); } }; }笔试题里如果只是让你写“防抖或节流”最好在代码前用注释说明两者区别再给出你的实现。面试官眼里这代表你不仅会写代码还理解它们的使用场景。4.3 手写题常见的坏习惯手写题有些坑是很多人会踩的。我在批改面试者的代码时见过不少典型问题这里列出来提醒大家变量命名随意a、b、c满天飞。哪怕是个简单函数变量名也应该具有自解释性面试官看代码时首先关注命名习惯。不处理边界参数。函数传进来的是空数组怎么办传进来的目标值不存在怎么办不处理这些情况代码健壮性就会扣分。只写主流程不考虑输出与返回。笔试是机器读题和面试官人工审题并存返回值的类型和格式需要和题目要求完全对齐。写完不检查。最简单的查找Bug方式就是拿一个用例手动走一遍尤其是循环条件的边界值在交卷前花30秒验证一下往往能救回一道题。5. 浏览器与网络高频考点梳理5.1 HTTP缓存机制必须能画出来浏览器缓存题在笔试里的出现率几乎是100%。字节的笔试题通常会考强缓存和协商缓存的区别、相关响应头有哪些、状态码是什么。强缓存浏览器请求资源时先检查本地缓存是否过期。如果没过期直接用缓存不会发起网络请求。相关头是Expires和Cache-Control其中Cache-Control的优先级更高它的max-age字段以秒为单位表示缓存多长时间。协商缓存当强缓存过期后浏览器会向服务器发起请求带上之前缓存的资源标识。服务器根据标识判断资源是否发生变化。如果没变返回304浏览器继续用本地缓存如果变了返回200和新资源。相关头是Last-Modified/If-Modified-Since和ETag/If-None-Match其中ETag的优先级高于Last-Modified。一个完整的答题思路最好能说清楚整个流程浏览器第一次请求资源服务器返回200和缓存头浏览器保存资源。第二次请求时浏览器判断缓存是否新鲜新鲜则直接使用本地副本不请求服务器。不新鲜则带上协商头重新验证服务器返回304则继续用本地缓存返回200则更新缓存。这个考点背后对应的是真实性能优化场景。首页加载慢、静态资源不更新、CDN回源问题都和缓存策略直接相关。5.2 跨域方案要掌握CORS的核心跨域题也是笔试题的常客。原因很简单前后端分离的架构下几乎每个项目都会遇到跨域问题。最常见的方案是CORS也就是后端在响应头里加Access-Control-Allow-Origin。简单请求和非简单请求的区别要注意简单请求包括GET、POST、HEAD且Content-Type为表单类型非简单请求会先发一个OPTIONS预检请求服务器确认允许后才能发出正式请求。这个预检逻辑如果说不清楚跨域题就丢分一半。JSONP是另一种经典方案利用script标签不受同源策略限制的特性动态插入script标签加载接口接口返回一个回调函数调用。JSONP只支持GET请求安全性也不如CORS如今使用得越来越少但作为历史方案依然可能出现在选择题里。还有几个跨域方案要能说出名称和原理代理服务器转发、location.hash iframe、postMessage。工作中最常见的其实就是Vite或webpack的devServer代理以及nginx反向代理。理解跨域的本质是浏览器同源策略的约束服务器之间是没有跨域问题的。5.3 从URL输入到页面渲染的完整链路这是一道综合性很强的简答题。它可以拆成网络和渲染两个阶段。网络阶段输入URL后浏览器先解析URL检查缓存然后是DNS解析拿到IP地址建立TCP连接发起HTTP请求服务器返回HTML响应。如果是HTTPS中间还多一步TLS握手。渲染阶段浏览器拿到HTML后开始解析HTML构建DOM树解析CSS构建CSSOM树两者合成渲染树。浏览器根据渲染树计算布局也就是每个节点的几何位置然后绘制到屏幕上。JavaScript会阻塞HTML解析遇到script标签时会先下载并执行脚本这也是为什么script标签要放到body底部或者使用async和defer。在此基础上如果再问到重排和重绘的区别要能答出重排是布局发生变化比如宽度、位置调整代价更高重绘是样式变化但不影响布局比如颜色和背景代价较低。优化思路是减少DOM操作、批量更新、使用transform替代top/left动画。这道题比较考验知识面的完整度能够分阶段、按顺序、结合状态码和渲染细节回答完整的同学并不多见所以在笔试和面试里都很能拉开区分度。5.4 本地存储三件套的适用场景localStorage、sessionStorage、cookie是笔试选择题的常客要能把它们的区别说清楚。cookie的大小限制在4KB左右每次请求都会自动携带在请求头里适合做登录凭证。它有过期时间可以由服务器通过Set-Cookie下发也可以由前端设置。localStorage大小限制在5MB左右持久化存储不会自动过期适合存用户的偏好设置、不敏感的业务配置。它不会自动携带在请求头里需要JS手动读取。sessionStorage和localStorage的区别在于作用域和生命周期。sessionStorage在标签页关闭后就清除了而且只在当前标签页有效适合存临时性的页面状态。再深入一层面试官会问这些存储的安全性问题。cookie如果不设HttpOnly脚本就能通过document.cookie读取存在XSS窃取风险。localStorage被XSS攻击时同样会被盗取。所以不要把敏感信息放在localStorage这是前端安全的基本常识。6. 笔试题之外的高分技巧6.1 考场时间分配和答题顺序笔试时间有限建议先快速浏览全部题目把会的题先做完难题留到最后。前10分钟用来做选择填空因为这类题通常一两分钟一道拿分效率最高。手写代码题控制在60分钟左右每道题先写思路框架再填代码。如果遇到完全不会的简答题不要留白。把相关知识点梳理出一个逻辑链写上去哪怕说不精确也至少让面试官看到你的思考方向。比如不会做深拷贝可以先写出JSON方案再补充它的局限性然后给出递归实现思路。能写出思路骨架也比交白卷强很多。另外一个很重要的建议手写代码前先写注释说明思路。这不仅仅是给面试官看也是给自己理清逻辑。一份代码哪怕最终不完全正确只要有清晰的结构和注释面试官往往会根据思路给分。6.2 从笔试题反推复习重点这套2017年的笔试题虽然已经过去好几年但它的核心知识点到今天依然是前端面试的主要命题范围。如果你想用它来准备当前的大厂面试可以按照这个优先级复习JavaScript基础是重中之中闭包、this、原型链、事件循环、异步编程这些概念要做到能讲、能写、能画图。CSS布局至少掌握flex和grid的完整用法垂直水平居中和BFC要能默写出3种以上方案。算法题重点准备数组去重、深拷贝、防抖节流、二分查找、递归转迭代这些经典题。网络基础要能完整串起来缓存、跨域、从输入URL到页面渲染是三个必须讲透的话题。框架部分要结合你简历上的项目把Vue或React的响应式原理、生命周期、组件通信方案准备清楚。字节的面试特别看重“为什么用这个”和“出现了问题怎么办”所以任何方案都要能说出理由。6.3 一套可复用的笔试自查清单在真正笔试之前我建议你自己列一个自查清单不要在开考后才想起来。以下是我个人实践下来比较有效的一份清单数组和字符串有哪些常用方法返回值是什么。闭包的优缺点分别是什么什么时候会内存泄漏。this指向的判断口诀是什么。Promise和async/await的互相转换是否熟练。事件循环的宏任务、微任务有哪些。常见HTTP状态码的含义尤其是301、302、304、400、401、403、404、500。强缓存和协商缓存的完整请求流程。垂直水平居中的四种写法。flex的属性是否都已经背熟。深浅拷贝的边界情况是否清楚。防抖和节流的代码能否不查资料写出来。数组去重和排序的常见写法能不能默写。这些问题都能在5分钟之内快速过一遍。每周做一次自测直到不需要思考就能答出来笔试基本就稳了。最后再多说一句笔试只是第一关并不是说笔试满分就能拿到offer。真正重要的是你在笔试里展示出来的思考过程和代码习惯这决定了面试官愿不愿意让你进入下一轮。所以千万不要为了刷题而刷题学完一个知识点多问自己一句“为什么”和“什么场景下要用”这样才能真正把题目的价值榨干。这套2017年的题目带给我的不是几道题的答案而是对前端基础知识体系的一次完整梳理。希望你也能从这套题里得到同样的收获。