ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

前端面试必刷:从京东笔试题看JavaScript底层原理与异步编程

前端面试必刷:从京东笔试题看JavaScript底层原理与异步编程 先说个结论这份卷子放到今天考点依然没有过时。我当年刷这套题的时候还在用jQuery写业务看到“请写出一个符合Promise/A规范的实现”整个人是懵的。等到后来真正啃完红宝书、看完Vue源码、把浏览器渲染机制捋顺了再回头看才意识到这份笔试题考察的并不是某个框架的API背诵而是前端工程师最底层的通用能力语言特性、异步模型、浏览器原理、网络协议、框架设计思维。这篇文章我不会只贴答案而是把每一类考点背后的“为什么”拆开揉碎结合我这些年面试别人和被别人面试的经验讲清楚哪些是送分题、哪些是拉分题、哪些是你必须形成肌肉记忆的底层逻辑。无论你是准备校招的应届生还是想查漏补缺的初中级工程师这份拆解都能帮你把知识体系重新梳理一遍。1. 整体设计思路与考点分布解析1.1 为什么2018年的题现在依然值得刷很多候选人问我“面试题更新换代这么快刷几年前的真题还有用吗”我的回答一直是看你怎么刷。前端这个行业表面上层出不穷的框架、工具链、构建方案天天在变但底层的东西几乎没变过JavaScript依然是单线程语言事件循环机制依然是那套宏任务微任务的规则浏览器依然按照那几步渲染页面HTTP缓存依然围绕强缓存和协商缓存展开。京东这套2018年的笔试题恰好精准地覆盖了这些“不变”的核心。更重要的是这套题暴露了一个筛选逻辑大厂要的不是“会用Vue/React写页面”的人而是“理解前端底层原理”的人。笔试题里大量出现原生JS实现、浏览器机制、网络协议这类题目恰恰是因为这些能力无法靠包装简历来伪装必须静下心啃过源码、踩过坑、做过性能优化才能答得出来。1.2 考点分布与难度梯度梳理把整套笔试题按主题拆开大致是这样一个分布考点领域典型题型难度考察目的JavaScript语言特性typeof类型判断、原型链分析、作用域与闭包、深拷贝与浅拷贝低中语言基本功是否扎实异步编程Promise应用与实现、事件循环输出顺序、async/await中高是否真正理解异步执行模型浏览器与渲染回流与重绘、页面渲染流程、事件冒泡与捕获中是否具备性能优化意识网络协议HTTP状态码、缓存策略、跨域方案、HTTPS握手中日常开发遇到问题能否排查框架与工程化响应式原理、虚拟DOM、模块化规范、构建工具高是否停留在API使用层面手写代码与算法数组去重、防抖节流、继承实现、排序中高编码能力与代码质量从难度梯度来看这套题有明显的分层设计前面的选择题、填空题主要过滤“完全没准备”的人中段的简答题开始筛选“有一定深度”的人最后的手写题则是在筛选“真正写过高质量代码”的人。很多人挂在手写题上不是因为不会而是因为平时习惯用框架把JS原生能力退化掉了。1.3 从这份卷子反推大厂的用人标准站在面试官的角度倒推你会发现这套笔试考察了三个核心能力刚好对应前端工程师的不同成长阶段第一是语言精通度。typeof null为什么是object闭包的内存泄漏怎么避免这些看似偏门的细节直接反映你有没有系统性地学习过JavaScript而不是零散地抄代码。第二是技术深度。Promise是怎么实现的虚拟DOM的diff算法怎么工作这些不是日常写业务能接触到的必须刻意去啃源码。第三是解决问题的能力。跨域怎么处理首屏加载怎么优化这些题没有标准答案考察的是你面对实际问题时的思路完整度。所以我会在后面几个部分里按照这套逻辑逐个击破帮你把每一类考点都转化成可复用的知识和实战经验。2. JavaScript基础一网打尽所有送分题与拉分题2.1 类型判断与数据类型typeof、instanceof、Object.prototype.toStringJS数据类型这块几乎是所有前端面试的“开胃菜”但能完全答对的人真的不多。京东这套题里关于类型判断的题目属于典型的看着简单、实则暗藏杀机。先说typeof的经典坑位typeof null // object typeof function(){} // function typeof NaN // number typeof [] // objectnull返回object是JS从诞生之日起就存在的BugECMAScript规范为了兼容性一直没修复。面试官问这个不是为了考你背结论而是看你对“JS设计缺陷”有没有认知。实际开发中判断null值的正确姿势是使用Object.prototype.toString.call(null)或者更直接地使用value null。instanceof的底层原理其实是通过原型链查找它判断的是“构造函数的prototype对象是否出现在实例的原型链上”。这里有个很关键的问题[] instanceof Object返回true因为数组的原型链最终会指向Object.prototype。所以instanceof不能精确判断具体类型只能判断“是不是某个构造函数的实例”。真正精确的类型判断只有一个方案function getType(value) { return Object.prototype.toString.call(value).slice(8, -1); } getType([]); // Array getType({}); // Object getType(new Date()); // Date getType(null); // Null getType(undefined); // Undefined getType(new Map()); // Map这个方法的原理是利用了Object.prototype.toString的内置标签机制任何对象调用这个方法都会返回[object Type]格式的字符串。在日常开发中封装一个通用的类型判断工具函数我强烈建议用这个方案而不是依赖typeof和instanceof的组合判断。2.2 深拷贝与浅拷贝从手写实现到工程化方案拷贝问题是面试里出现频率极高的手写题因为它在实际开发中确实经常用。先说清楚两者的区别浅拷贝只复制对象的第一层属性如果属性值是引用类型拷贝后依然共享同一个内存地址深拷贝则是递归复制所有层级的属性生成一个完全独立的对象。浅拷贝的实现方式有很多展开运算符、Object.assign、数组的slice和concat都属于浅拷贝。很多人以为展开运算符是深拷贝其实不然let obj { a: 1, b: { c: 2 } }; let shallowCopy { ...obj }; shallowCopy.b.c 100; console.log(obj.b.c); // 100被改了深拷贝的经典手写版本是递归实现但有几个非常容易漏掉的处理function deepClone(target, map new WeakMap()) { if (typeof target ! object || target null) { return target; } // 处理日期和正则 if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target); // 解决循环引用 if (map.get(target)) return map.get(target); const cloneTarget Array.isArray(target) ? [] : {}; map.set(target, cloneTarget); for (let key in target) { if (target.hasOwnProperty(key)) { cloneTarget[key] deepClone(target[key], map); } } return cloneTarget; }这里用WeakMap解决循环引用问题是最关键的不然面对obj.self obj这样的结构会直接造成调用栈溢出。工程化场景里如果是Vue项目直接用lodash的cloneDeep最省事但面试时你必须手写得出来还要说清楚为什么用WeakMap而不是Map——因为WeakMap的键是弱引用不会阻止垃圾回收机制避免内存泄漏。2.3 闭包与作用域链从执行上下文的角度重新理解闭包是JS面试的一个分水岭很多人在业务代码里用过闭包但真让你讲清楚“什么是闭包”反而支支吾吾。闭包的本质是函数与其词法作用域的绑定关系。当内部函数引用了外部函数的变量并且这个内部函数在外部函数执行完毕之后仍然被引用比如被return出来、被赋值给全局变量、被添加到事件监听器中就形成了闭包。此时外部函数的变量不会被垃圾回收因为内部函数的作用域链上仍然保留着对它们的引用。京东这类大厂笔试题喜欢结合循环来考闭包for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); } // 输出 5 5 5 5 5这个经典问题的原因在于var声明的i是函数级作用域循环结束之后i已经变成5而setTimeout的回调是在循环结束后的1秒才执行此时读取的i是全局作用域里的同一个i。解决方案有三种用let声明块级作用域、用闭包包裹一层立即执行函数、或者把值作为参数传给setTimeout的第三个参数。我在实际面试中更看重候选人能否从“执行上下文”和“作用域链”两个维度解释闭包。当你调用一个函数时JS引擎会创建一个执行上下文其中包括变量对象、作用域链和this指向。函数内部查找变量时会沿着作用域链一层层往外找直到全局作用域。闭包之所以能访问外部函数的变量就是因为它创建时把外部函数的变量对象挂在了自己的作用域链上。2.4 this指向问题四大绑定规则与常见陷阱this指向问题绝对能排进前端面试“翻车率最高”的前三名。很多人喜欢死记硬背“谁调用指向谁”但实际题目一换场景就错。this的指向遵循四条规则按优先级从高到低排列第一new绑定。使用new调用构造函数时this指向新创建的实例对象。第二显式绑定。通过call、apply、bind方法调用时this指向传入的第一个参数。第三隐式绑定。作为对象的方法调用时this指向该对象。第四默认绑定。独立函数调用时在非严格模式下this指向全局对象浏览器里是window严格模式下是undefined。优先级覆盖的例子如果用call显式绑定了一个对象同时又用new调用构造函数new的优先级更高。箭头函数完全不适用以上四条规则。它的this是在定义时从外层作用域捕获的一旦确定就无法改变即使call、apply、bind也无效。这个特性让箭头函数特别适合用在setTimeout回调、事件处理函数、数组遍历的回调等场景因为不用担心this被意外改写。这里给大家一个亲测有用的记忆方式不用管“谁调用指向谁”而是判断函数调用时有没有“点号”有的话this指向点号前面的对象没有点号就看有没有call/apply/bind都没有就看是不是被new了最后能且只能是默认绑定。箭头函数单独记忆永远看外层作用域。3. 异步编程与事件循环从Callback到Promise再到async/await3.1 事件循环机制宏任务与微任务的完整执行流程异步编程是前端面试的重灾区也是京东这套笔试题里区分度最高的部分之一。我之前面过不少人问他“事件循环是什么”能答出宏任务微任务但让他分析一段代码的输出顺序十有八九出错。直接上一个最经典的例子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这个输出顺序的背后是事件循环的完整机制。JS引擎在每一轮循环中先从宏任务队列里取出一个任务执行执行过程中如果遇到微任务Promise.then、MutationObserver等会把它们添加到微任务队列。当前宏任务执行完毕后会一次性清空整个微任务队列然后再从宏任务队列里取下一个任务。setTimeout即使延迟设为0也要等当前宏任务和所有微任务全部执行完才会被取出来。这就是为什么上面代码里Promise的回调总是先于setTimeout执行。注意一个细节所谓“一次清空微任务队列”指的是在宏任务执行期间新增的微任务也会被一并执行因此如果微任务里不断添加新的微任务就会导致宏任务一直无法执行造成页面卡死。在这个机制的基础上理解async/await就简单了。async函数返回的本质上是一个Promiseawait会阻塞当前异步函数的执行把后面的代码包装成微任务。更底层的理解是await后面表达式的返回值会被Promise.resolve包裹然后当前函数的剩余代码会被注册为这个Promise的then回调。3.2 Promise的状态机与链式调用不只是背APIPromise考察通常分两个层次基础API的使用以及手动实现一个符合Promise/A规范的最小版本。前者只要写过项目基本没问题后者则是真正的分水岭。先说状态机。一个Promise对象必然处于以下三种状态之一pending等待中、fulfilled已成功、rejected已失败。状态只能从pending变为fulfilled或rejected一旦改变就不可逆。这也是Promise设计最核心的地方——它解决了回调地狱问题让异步操作的状态变化可以被统一管理。链式调用的本质是每次then都会返回一个新的Promisenew Promise((resolve, reject) { setTimeout(() resolve(1), 1000); }).then(res { console.log(res); // 1 return res 1; }).then(res { console.log(res); // 2 throw new Error(出错了); }).catch(err { console.log(err.message); // 出错了 });这里有个容易忽略的点第二个then里虽然没有显式return但它内部返回的实际上是undefined也会被包装成Promise.resolve(undefined)。所以在链式调用里每个then都要记得返回一个值或Promise否则下一个then拿到的就是undefined了。Promise的经典手写实现题核心要点是解决三个问题状态只能改变一次、then支持链式调用、异步执行resolve后能通知所有then回调。一个精简但完整的实现版本需要用到发布订阅模式把then注册的回调放在一个数组里等resolve被调用时再逐个执行。如果你的手写实现还能处理“状态已经改变后注册的then也能立即拿到结果”的情况那就基本到位了。3.3 async/await语法糖背后的错误处理与并发控制async/await是Promise的语法糖但它让异步代码的编写方式从回调风格彻底转向同步风格可读性提升了一大截。不过面试题里考async/await往往不会只考用法而是结合事件循环考执行顺序或者结合错误处理考异常捕获。错误处理是最容易被忽视的点。async函数内部如果抛出异常返回的Promise会进入rejected状态。所以必须用try/catch包裹await调用async function fetchData() { try { const res await fetch(/api/user); const data await res.json(); return data; } catch (error) { console.error(请求失败, error); return null; } }这里有个容易踩的坑如果await后面跟的Promise被reject了而外层没有try/catch异常会被静默吞掉或者变成Unhandled Promise Rejection在Node环境里直接导致进程退出在浏览器里表现为控制台报错非常难排查。所以写async函数有个习惯必须养成要么所有await都有try/catch包裹要么在调用async函数的地方统一catch。并发控制也是面试常考的点。用Promise.all可以并行执行多个异步操作const [user, list] await Promise.all([ fetch(/api/user), fetch(/api/list) ]);但如果其中一个失败Promise.all会整体reject。如果希望每个请求互不影响可以用Promise.allSettled。对于有并发数量限制的场景比如一次性请求100个图片资源的缩略图还需要自己实现一个带并发池的控制函数这是很多大厂笔试题的进阶版本。4. 浏览器机制与网络协议从页面加载到性能优化4.1 从输入URL到页面渲染完整链路拆解“从输入URL到页面渲染经历了什么”是前端面试的必考题京东这套题也在不同角度反复考察了这条链路。这个问题没有绝对标准的答案但覆盖的知识点越完整、越有层次说明候选人对浏览器原理的理解越深。完整链路大致分为六个阶段DNS解析、TCP连接、HTTP请求、服务器响应、浏览器解析渲染、页面加载完成。面试时至少要答出每个阶段的关键细节。DNS解析是把域名解析为IP地址的过程会依次查找浏览器缓存、系统缓存、路由器缓存、根DNS服务器、顶级域名服务器、权威DNS服务器。这个过程如果命中了缓存会非常快这也是为什么预解析link reldns-prefetch能优化首屏加载速度。TCP连接阶段要讲清楚三次握手客户端发送SYN包、服务器回复SYNACK包、客户端再发送ACK包连接建立。对于HTTPS站点还需要加上TLS握手过程。HTTP请求阶段要讲清楚请求行、请求头、请求体。更关键的是缓存策略强缓存Cache-Control、Expires命中则直接使用缓存不发请求协商缓存Last-Modified、ETag则带着条件请求头去服务器验证。浏览器解析渲染阶段是这个问题的重头戏下面单独展开。4.2 回流与重绘从渲染原理到性能优化浏览器从拿到HTML字节流到绘制出页面经历了以下步骤解析HTML构建DOM树、解析CSS构建CSSOM树、合并两棵树生成Render Tree、布局计算每个节点的几何位置、绘制到屏幕上。这里最核心的概念是回流Reflow和重绘Repaint。回流指的是布局阶段需要重新计算元素的几何位置和尺寸重绘指的是不影响布局只影响外观的重新绘制。回流的代价远高于重绘因为回流必然触发重绘而重绘不一定触发回流。哪些操作会触发回流读取或修改offsetWidth、clientWidth、getBoundingClientRect等属性时浏览器会被强制同步计算布局增删DOM节点改变元素尺寸、位置窗口尺寸变化字体大小变化。哪些操作只触发重绘改变背景色、文字颜色、可见性等不影响布局的属性。优化的核心思路是减少回流次数。常用的手段包括使用class批量修改样式而不是逐个修改style属性将元素设置为display:none后修改再显示使用DocumentFragment批量操作DOM对动画元素使用transform代替修改top/left位置。transform之所以高效是因为它属于合成层操作不触发回流和重绘GPU直接处理。面试时如果被问到“为什么transform比top性能好”你至少要答出修改top会触发布局计算而transform是在合成阶段对像素做变换不涉及布局。如果还能补充“compositor只处理合成层动画跑在GPU上主线程可以继续处理JS”这个答案就能打到面试官心里。4.3 跨域方案全景从CORS到代理再到postMessage跨域是前端开发绕不开的话题也是笔试题里必考的场景题。浏览器的同源策略规定协议、域名、端口任何一个不同都是跨域默认不允许读取对方的响应。实际开发中跨域解决方案有很多按使用频率排个序第一是CORS跨域资源共享。这是标准的解决方案核心是服务器在响应头里加Access-Control-Allow-Origin: 指定域名或*。非简单请求需要先发一次OPTIONS预检请求。很多候选人只知道加响应头但不知道“预检请求”是怎么触发的——当请求方法不是GET/HEAD/POST或者Content-Type不是三种简单类型之一或者带了自定义请求头时浏览器会自动发出OPTIONS预检。第二是Nginx反向代理。因为同源策略是浏览器层面的限制服务器之间的请求不受影响。通过Nginx把前端的API请求代理到后端服务器前端请求的是同源地址自然就不存在跨域问题。这是生产环境最常用也最推荐的方式。第三是JSONP。利用script标签不受同源策略限制的特性通过动态插入script标签来实现跨域请求。但它只支持GET请求而且有安全性问题现在已经用得很少了但笔试题偶尔还是会考。第四是postMessage。主要用于iframe跨域通信比如嵌入了第三方支付页面父页面需要接收子页面传递的数据。我在面试候选人时如果对方能主动说出CORS的预检请求条件、Nginx代理的原理、以及“前端上线后遇到跨域问题应该先确认接口走的是不是代理”说明他真的是在项目里碰过壁的这种实战经验比背十个方案都值钱。4.4 HTTP缓存与状态码排查线上问题的基本功HTTP这块笔试题通常考两类一类是状态码的含义另一类是缓存策略的细节。状态码里几个高频考点是301永久重定向、302临时重定向、304协商缓存未修改、400请求参数错误、401未认证、403禁止访问、404不存在、500服务器内部错误、502网关错误、504网关超时。这里有个非常容易踩的坑很多人把301和302搞混实际上301是永久重定向浏览器会缓存这个跳转302是临时重定向每次访问都会重新请求服务器。缓存策略的考察点是强缓存和协商缓存的区别。强缓存相关的响应头是Cache-Control和Expires命中后直接从本地缓存读取HTTP状态码是200from disk cache。协商缓存相关的响应头是Last-Modified和ETag缓存失效后会携带If-Modified-Since或If-None-Match去服务器验证服务器返回304则使用本地缓存返回200则用新资源。按优先级来说Cache-Control高于ExpiresETag高于Last-Modified。实际项目里我一般用Cache-Control: max-age31536000来缓存带hash的文件用Cache-Control: no-cache来处理index.html这种需要实时验证的入口文件。这套组合拳基本能解决大部分静态资源的缓存更新问题。顺便提一嘴很多开发者在本地调试时遇到“改了代码但页面不生效”的问题八九成是强缓存命中解决方案是在Network面板勾选Disable cache或者在请求URL后面拼时间戳参数。5. 框架原理与工程化从API使用到源码解读5.1 Vue响应式原理Object.defineProperty的局限与Proxy的改进京东这套笔试题出现框架题的可能性很高尤其是Vue的响应式原理基本上属于必考级别的高频题。Vue 2的响应式核心是利用Object.defineProperty对data对象的每个属性进行getter/setter的劫持。组件初始化时Vue会递归遍历data对象的每个属性把它们转换成getter和setter。当组件读取某个属性时getter会收集当前的Watcher作为依赖当属性被修改时setter会通知所有收集的Watcher进行更新。这个设计的优点是简单可靠但有几个明显局限无法检测对象属性的新增和删除所以Vue提供了Vue.set和Vue.delete来处理无法检测数组索引的直接修改需要用数组的变异方法push、pop、splice等才能触发更新对象层级越深初始化递归的代价越大。Vue 3改用Proxy实现响应式完美解决了上述问题。Proxy可以拦截整个对象的读取、设置、删除、遍历等操作因此新增属性和删除属性都能被感知。同时Proxy采用惰性代理只有属性被访问到才做深层代理初始化性能比Vue 2好很多。面试时如果被问到这个话题我建议你补充一个“为什么Proxy能监听数组而defineProperty不行”的细节defineProperty只能对已有的索引键做劫持但直接修改arr[0] x这种操作本身在Vue 2里是不会触发setter的因为数组的索引属性在初始化时根本没有被转换。而Proxy拦截set操作不管操作对象是数组还是对象只要是设置属性就会触发。5.2 虚拟DOM与diff算法为什么需要它以及如何高效更新虚拟DOM的考点在于你能否讲清楚“为什么需要虚拟DOM”。直接操作真实DOM代价很高因为每一次DOM操作都可能引发浏览器的回流和重绘。虚拟DOM的思路是用JS对象描述DOM结构通过对比新旧虚拟DOM的差异找出真正需要更新的部分然后一次性批量操作真实DOM。这里要澄清一个常见的认知误区虚拟DOM并不是一定比直接操作DOM快。如果只是单纯更新一个文本节点直接操作DOM肯定更快。虚拟DOM的核心优势在于当应用状态复杂、需要频繁更新大量节点时它能帮你把多次零散的DOM操作合并成一次精确的更新从而降低总体的性能损耗并且让开发者不必手动管理DOM操作逻辑。diff算法的核心思想是逐层对比这也是Vue和React共同的策略第一同层比较只比较同一层级的节点不做跨层级比较。第二tag不同直接替换如果两个节点的标签名不同直接创建新节点替换旧节点不再往下比较子节点。第三key的优化通过key可以识别同一个节点在列表中的身份从而避免没有必要的销毁重建。在列表渲染中用index当key是一个高频的坑。Vue官方文档明确指出不推荐使用index作为key。原因很简单当列表项被插入、删除、排序时index会发生变化导致虚拟DOM无法正确对应新旧节点可能引发状态错乱、复用错误。比如一个可勾选的列表在中间插入一条新数据后后面所有项的index都变了它们的勾选状态就全部错乱了。正确的做法是使用唯一且稳定的标识符作为key比如后端返回的id字段。如果列表是纯展示不可交互的用index勉强可以但一旦涉及状态维护必须换掉。5.3 模块化演进从IIFE到CommonJS到ES Module模块化是前端工程化的基石。从最早的全局变量污染问题到IIFE、AMD、CMD、CommonJS、ES Module每一次演进都在解决“如何组织代码”这个核心问题。IIFE立即执行函数是最原生的模块化方式利用函数作用域隔离变量把需要暴露的内容挂到window上。CommonJS是Node.js采用的模块规范核心是module.exports和require它是同步加载的模块因为服务器本地文件读取很快不会有性能问题。ES Module是ES6官方标准浏览器原生支持。它和CommonJS有个关键区别ES Module是静态导入在编译时就能确定依赖关系所以可以做tree-shaking摇树优化去掉没有被使用的导出而CommonJS是运行时加载无法提前分析依赖。这也是为什么现代构建工具都优先推荐使用ES Module进行开发。关于模块化的面试题还有一类是问“require和import的区别”。回答要点包括语法差异、加载时机运行时vs编译时、是否可动态引入、this指向不同、以及tree-shaking的先决条件。如果能补充“ES Module的导入是只读引用”更能体现深度。5.4 构建工具与工程化实践从Webpack到性能优化工程化考察的核心是Webpack尤其是loader和plugin的区别、打包优化的常见策略。loader和plugin的区别是基础但高频的题。loader本质上是一个转换器它把文件从一种格式转换成另一种格式比如babel-loader把ES6代码转成ES5css-loader处理CSS文件中的import和url引用style-loader把CSS注入到DOM中。plugin是插件它做的事情更广泛在Webpack生命周期的特定阶段执行比如HtmlWebpackPlugin自动生成HTML文件TerserPlugin压缩JS代码。打包优化的常见策略按收益排序第一代码分割Code Splitting。把第三方库Vue、React、Element等打包成单独的vendor chunk利用浏览器的长期缓存避免业务代码更新后第三方库也一起失效。第二按需加载Lazy Loading。路由懒加载是前端性能优化的重要一环配合webpack的动态import在访问对应路由时才加载对应的JS分包。第三tree-shaking。前提是使用ES Module语法去掉没有被引用的死代码。第四CDN加速。把静态资源上传CDN减少用户到源站的网络延迟。第五Gzip压缩。服务端开启Gzip一般能减少60%-70%的传输体积。面试时如果被问到Webpack的构建流程可以从初始化参数、编译打包、优化、输出这四个阶段来答如果还能说出“compiler和compilation两个对象的区别”说明你真的读过Webpack源码这在候选人里是非常大的加分项。6. 手写代码与场景题那些看似简单实则暗藏杀机的题6.1 高频手写题盘点与解题模板前端笔试的手写题翻来覆去就是那几个经典题型但就是有人写不出来。不是不会是平时没有刻意练。我把高频题整理成一个清单每个都给出核心思路数组去重。最简洁的是Array.from(new Set(arr))如果要求不依赖Set可以用对象做哈希表或者用indexOf判断或者用filter配合indexOf。面试时如果问“有没有更优方案”你可以回答用哈希表O(n)复杂度顺便提一下Set内部的实现原理。防抖和节流。这两个概念经常被搞混。防抖debounce是在事件触发后延迟执行延迟期间再次触发则重新计时适用场景是搜索框输入联想、窗口resize后的计算节流throttle是在一个时间窗口内只执行一次适用场景是滚动事件、按钮重复点击。// 防抖 function debounce(fn, delay 300) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; } // 节流 function throttle(fn, interval 300) { let last 0; return function(...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }深拷贝。第二部分详细写过了核心是递归加WeakMap解决循环引用。类型判断。用Object.prototype.toString是最稳的。Promise.all的实现。核心是返回一个新的Promise遍历传入的所有Promise用计数器统计完成个数全部完成则resolve任何一个是rejected就整体reject。call、apply、bind的实现。call和apply的核心思路是把函数挂到传入的this对象上调用即可。bind要复杂一些需要返回一个新函数还要考虑new调用的情况。6.2 场景题产品需求转化为技术方案的能力除了纯手写题还有一类经典的场景题比如“如何实现一个前端图片懒加载”“如何设计一个前端埋点系统”“如何优化首屏加载速度”。这类题没有标准答案考察的是你将需求拆解为技术方案的能力。拿图片懒加载举例我一般建议从三个层面回答基础思路是监听scroll事件判断图片是否进入视口getBoundingClientRect或IntersectionObserver进入视口后再把真实地址赋给src。进阶思路是使用IntersectionObserver替代scroll监听性能更好而且不需要手动做节流。补充思路是考虑默认占位图、加载失败后的兜底、以及预加载下一屏图片这种体验优化。面试官从这类题的答案里能看到你平时写代码时有没有系统性思考。我面试过很多候选人能写业务代码的很多但能把“需求-方案-实现-优化”这条路讲清楚的人十个里面不超过三个。所以平时写代码要有意识地在脑内做这个推演不止是完成任务而是想清楚为什么这样做、有没有更好的做法。6.3 一面二面三面的考察侧重从基本功到综合素质笔试通过之后面试才是真正的筛选环节。我总结一下各轮面试的侧重点帮你对号入座地准备。技术一面通常考察基础知识和编码能力范围就是上面分析的这些内容。面试官会从笔试卷子上的错题开始追问所以笔试完不是结束而是面试的起点。如果笔试里闭包题答错了面试时大概率会被追问“闭包是什么”“闭包有什么优缺点”“怎么避免闭包导致的内存泄漏”。技术二面开始考察项目深度和问题解决能力。考官会让你挑一个项目中遇到的最难的问题讲清楚背景、排查过程、最终解决方案。这里有个得分技巧讲问题时要突出“为什么难”然后展示你的排查思路——怎么排除干扰因素、怎么定位根因、怎么验证修复方案。过程比结果重要因为面试官想看到的是你的思考方式。三面通常是技术负责人或总监面考察的是综合素质技术视野、学习能力、对业务的思考、团队协作能力。这时候技术细节问得少了但可能会抛一些开放性问题比如“你如何看待前端未来三年的发展方向”。这道题没有标准答案但切忌空谈概念最好结合你正在做的事来讲哪怕说的是“我正在关注WebAssembly因为我遇到了某个性能瓶颈”都比泛泛而谈强得多。7. 常见问题与面试通关技巧7.1 笔试前我需要复习到什么程度这是我在各个技术群里被问得最多的问题。我的建议是先做一遍真题把错题对应的知识点记录下来然后对着这份知识图谱一个一个过。核心是“形成肌肉记忆”而不是“读过就算理解”。举个例子事件循环的输出顺序题不仅要会分析标准题目还要能够推演出变体的答案。比如在Promise的then后再加一层Promise.resolve().then输出顺序怎么变如果setTimeout嵌套一层呢如果换成Node环境microtask和macrotask的执行顺序有什么不同如果能做到面对未知变体也能推导出正确结果说明你是真的理解了机制而不是背答案。另外一个经常被忽略的点是算法题要提前练。虽然前端笔试以JS和浏览器技术为主但部分大厂会加入简单的算法题比如字符串翻转、数组去重、斐波那契数列、二分查找。这些题难度不大但如果平时不练考试时容易在边界条件和时间复杂度的优化上卡壳。LeetCode热题100里挑Easy和Medium的一半刷一遍基本就够了。7.2 面试官评分时到底在找什么站在面试官角度我评分时最关注三个维度深度、广度和沟通能力权重各占三分之一。深度维度考察的是你对某个技术点的理解是否足够底层。比如Vue响应式原理能答到“getter收集依赖、setter触发更新”是及格能答到“Array.prototype上重写的方法用于触发数组更新”是良好能答到“为什么用Proxy替代defineProperty、惰性代理如何提升性能”是优秀。广度维度考察的是你接触过的技术领域的数量。这个不要求精深但至少要知道、用过、能说出优缺点。比如前端工程化、性能优化、测试、CI/CD、可视化、Serverless每个方向能聊上十分钟都算过关。沟通能力这个维度往往被候选人忽略。同一个问题有些人能讲得条理清晰、层层递进有些人讲了三分钟还没说到重点。建议平时练习用一种结构化的方式输出答案先说结论再解释原因最后补充细节。这种模式在面试中非常占优势因为面试官一天面很多人听你说得清楚他不会觉得你在背书反而会认为这个能力在团队协作中很有价值。7.3 独家避坑笔试和远程面试中的实战细节最后分享几个从实战中踩出来的坑希望你们别走弯路。笔试环节最痛苦的不是不会写而是会写但没写全。比如手写Promise的时候只写了resolve的流程漏了reject的处理写深拷贝的时候忘记了循环引用写防抖的时候忘了保留this指向。这些都是白丢的分特别可惜。我的习惯是手写题完成后自己在脑子里跑两个测试用例一个是正常情况一个是边界情况null、空数组、循环引用确保代码经得起推敲。远程面试环节一定要提前调试好环境。我见过不止一个候选人面试时因为摄像头没开、麦克风没声音、或者共享屏幕失败而手忙脚乱。这些小事会直接影响第一印象建议面试前半小时就进入会议链接测试设备顺便想好一个安静、背景整洁的角落。项目深挖环节千万别说“这个项目是我一个人负责的”这种话。真实项目里前端不可能完全绕开后端、产品、测试说“一个人负责”只会让面试官觉得你缺乏团队协作经验。更好的说法是“我主要负责前端部分和后端同学确定了接口约定配合测试做了…”这类既能突出你个人贡献又能体现协作能力的表述。最后如果你拿到了笔试通知不要太焦虑。大厂笔试虽然难但它考察的就是本文梳理的这些基础能力。把这些知识点逐个打通你通过笔试的概率会大幅提升。同时这套面试题分析本身也是一次很好的知识复盘对照看看自己在哪些模块还有薄弱环节趁着准备面试的机会把短板补齐。这个查漏补缺的过程比拿Offer本身还有价值。
返回列表