那年杏花微雨,我还在第一家公司做前端,简历上写着“熟练掌握Vue”,面试官问我“Vue的响应式原理是什么”,我支支吾吾说了句“就是那个…数据变了页面就变了”,然后就没有然后了。后来我刷了三个月八股文,才发现当年那些让我栽跟头的问题,其实是有规律可循的。这个系列就是想把这些规律整理出来,用连续剧的方式,一集一集地把前端面试八股文讲透。
这一集是“初相遇”,也就是整个知识地图的开篇。我们不急着背题目,先搞清楚前端面试到底在考什么、八股文在整个面试中占什么位置、哪些知识点是必须拿下的地基,然后我会挑几个最核心的基础问题,从面试官视角拆给你看。适合准备校招、社招跳槽、以及想系统梳理前端基础的同学。
1. 前端八股文到底在考什么
1.1 面试官不是要你背书,是要你证明“能干活”
很多人一提八股文就皱眉,觉得面试官就是在刁难人。我跟不少面试官聊过,也在面试官的位子上坐过,说实话,大家平时写业务代码都忙得要死,没有谁闲到专门出题为难你。问八股文的真正目的,是快速筛掉那些简历写得天花乱坠、实际一问三不知的候选人。
你想啊,简历上写着“精通JavaScript”,那面试官总得验证一下吧。怎么验证?最快的方式就是问几个概念性的问题,看你能不能讲清楚。如果你连“闭包是什么”都说不上来,那“精通”两个字怎么让人信服?所以说,八股文本质上是一道道“质量抽检题”,不是考察你背诵能力,而是考察你有没有真正理解这门技术。
但这里有个关键区别:背书能过的是初级面试,理解后才能过中高级面试。同一个问题,“闭包是什么”和“闭包在实际项目里用来做什么、有什么坑”,是两个完全不同的考察维度。后者才是真正拉开差距的地方。所以这个系列里,每道题我都会带着场景讲,不会只给你一个干巴巴的定义。
1.2 前端面试知识地图:从地基到框架
前端面试的知识体系,大致可以分成七个区块,我按考察频率和重要性排个序:
- JavaScript核心机制(原型链、闭包、作用域、this、事件循环)
- 浏览器工作原理(渲染过程、回流重绘、缓存、存储)
- HTML与CSS基础(语义化、盒模型、布局方案、响应式)
- 网络基础(HTTP协议、HTTPS、TCP三次握手、跨域)
- 框架原理(Vue或React的响应式、虚拟DOM、生命周期)
- 工程化(模块化、打包工具、代码规范、部署流程)
- 数据结构和算法(高频手写题、复杂度分析)
为什么把JavaScript和浏览器放在最前面?因为这是前端的地基。框架年年换,今天Vue明天React后天可能又冒出来一个新东西,但JavaScript和浏览器的底层逻辑不会大变。你把这层打牢了,学任何框架都快;反过来,只学框架不抠基础,框架一升级你就慌。
这个系列会按这个地图一路展开。第一集“初相遇”先聊聊地图的前两站:JavaScript核心机制和浏览器工作原理,这两块是大多数公司一面必考的内容,先把它们啃下来,后面的路就好走了。
1.3 “连续剧”的追剧方式:别跳集,每集都要练
既然叫连续剧,那就有追剧的讲究。我的建议是:第一遍不要跳着看,按顺序把每一集的知识点过一遍,不求记住全部细节,先建立整体认知。第二遍再针对自己薄弱的模块重点刷,配合手写代码和项目复盘。
每一集后面我都会留一个“自查清单”,你可以在看完之后合上文章,试试自己能不能把每个问题讲出来。能讲出来不等于能扛住追问,所以最好找个人模拟面试,或者打开手机录音自己讲一遍,回放听听哪里卡壳了。这个方法很笨,但实测下来比单纯看有效得多。
另外说一句,八股文只是面试的一部分,现在已经很少有公司只问八股不问项目了。正确的心态是:八股文用来证明你的理论基础没问题,项目用来证明你确实干过活。两者缺一不可,别本末倒置。
2. 初相遇必考基础:JavaScript核心机制
2.1 原型与原型链
原型链是JavaScript里最基础也最容易被问懵的一个概念。很多人的理解停留在“每个对象都有__proto__,每个函数都有prototype”,但真要画张图,把实例、构造函数、Object.prototype之间的关系理清楚,就露馅了。
我给你们一个好记的理解方式:JavaScript里的继承,本质上是“对象之间的委托关系”。当你访问一个对象的属性时,它先在自己身上找,找不到就去__proto__指向的对象上找,再找不到就继续往上,直到Object.prototype,再往上就是null了。这条链就是原型链。
面试时经常让你手写实现一个new,其实考的就是原型链:
function myNew(Constructor, ...args) { // 创建一个新对象,把它的原型指向构造函数的 prototype const obj = Object.create(Constructor.prototype); // 执行构造函数,把 this 绑定到新对象上 const result = Constructor.apply(obj, args); // 如果构造函数显式返回了对象,就返回那个对象;否则返回新创建的对象 return result instanceof Object ? result : obj; }这段代码里的Object.create(Constructor.prototype)就是关键,它把新对象的原型挂到了构造函数的prototype上,从而让实例能沿着原型链访问到构造函数原型上定义的方法。手写的时候记得处理构造函数返回对象的情况,这是个经典的加分点。
追问的时候,面试官可能会问“Function.prototype和Object.prototype谁在上层”这类深水区问题。这里有个规律:所有函数都是Function的实例,所有对象最终都指向Object.prototype,而Function.prototype本身也是一个对象,所以它的__proto__指向Object.prototype。这条关系链建议自己画一遍,画明白了原型链这关就过了。
2.2 闭包:从原理到内存泄漏
闭包的定义很简单:函数能够访问它定义时所在作用域里的变量。但每次面试都能刷掉一批人,原因在于很多人只会背定义,不会讲场景。
我常用一个生活化的类比解释闭包——就像是随身带了一个背包。函数本来执行完就该把局部变量丢掉,但如果它返回了一个内部函数,而这个内部函数还“背着”外部函数的变量,那这些变量就不能被回收,跟着内部函数一起存活下来。
面试里最常见的问题是“用闭包实现一个计数器”:
function createCounter() { let count = 0; return function () { count++; return count; }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2这个例子里的count变量,正常情况下在createCounter执行完就该被回收了。但因为返回的函数还引用着它,所以它一直存在。这就是闭包的核心特征:外部函数执行完,内部函数仍然持有外部作用域的变量引用。
进阶问题是闭包和内存泄漏的关系。闭包本身不一定会造成内存泄漏,但如果闭包长期持有一个大对象,或者被意外地挂到了全局/事件监听器上,就会导致这部分内存无法释放。比如在Vue组件里用addEventListener绑定了回调,组件销毁时忘了移除监听,回调里的闭包引用就一直存在,这就是典型的泄漏场景。答题时加上这个例子,面试官会觉得你是真踩过坑的。
2.3 this 指向:四种绑定规则全拆解
this指向是前端面试八股文里最经典的“翻车点”之一。很多人靠死记硬背“箭头函数没有自己的this”,但换个问法就懵了。实际上,this的指向完全由调用方式决定,跟你函数定义在哪里无关。
整理成四个规则,按优先级从高到低:
- new绑定:用
new调用构造函数,this指向新创建的对象 - 显式绑定:
call、apply、bind可以手动指定this - 隐式绑定:作为对象的方法调用,this指向这个对象
- 默认绑定:独立函数调用,非严格模式下this指向
window(浏览器中),严格模式下是undefined
箭头函数不在这四个规则里,它不绑定this,而是继承外层作用域的this。这道题的关键是,当你写一个箭头函数时,它的this在定义时就确定了,之后无论怎么调用都不会变。
面试官很爱出一道综合题,让你判断下面这段代码的输出:
const obj = { name: 'obj', getName() { return this.name; }, }; const fn = obj.getName; console.log(fn()); // 输出什么?答案是undefined(严格模式下会直接报错)。因为fn被赋值给一个变量后再独立调用,这时候是默认绑定规则,this指向的是window(非严格模式),而window.name通常是不存在的。很多人看到obj.getName就以为是obj,其实关键在于调用时的形式:obj.getName()和const fn = obj.getName; fn()是完全不同的两种调用方式。这个点搞明白了,this这块基本就稳了。
2.4 事件循环:微任务和宏任务的执行顺序
事件循环是现在前端面试的必考题,尤其是考察你对setTimeout、Promise、async/await的理解程度。很多人在浏览器里写代码没问题,但一让排执行顺序就脑袋冒汗。
你只需要记住两条铁律:
- 同步代码先执行,遇到异步就挂到对应的队列里
- 每次事件循环,先清空微任务队列,再取一个宏任务执行,然后再清空微任务队列
宏任务包括setTimeout、setInterval、setImmediate、I/O 操作;微任务包括Promise.then、MutationObserver以及async/await中的后续代码。
来看一道经典题:
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。为什么promise1在setTimeout之前?因为setTimeout是宏任务,即使延迟是0,也要等当前宏任务执行完、微任务队列清空后才会被取出执行。
再提醒一个容易踩坑的地方:async/await里的await后面部分等同于.then()里的回调,属于微任务。有时候面试官故意出个await和Promise.then混在一起的题,你只要记住“等号右边先同步执行,await后面的代码异步执行”这个原则,基本不会错。
3. 浏览器工作原理:从输入URL到页面渲染
3.1 一次完整的URL请求发生了什么
这是前端面试里出镜率最高的一道综合题。它好考的地方在于,一个题目能串起网络、浏览器、渲染引擎等多个知识点。面试官问这个问题,看的不是你背得多熟,而是你能不能按层次把过程讲清楚,遇到追问能不能深入。
我建议按照时间线来答,思路清晰也不容易漏:
- 输入URL后,浏览器先解析URL,判断协议、域名、端口,然后处理缓存(如果命中强缓存,直接走缓存流程,不发请求)
- DNS解析:把域名解析成IP地址,依次查找浏览器缓存、系统缓存、路由器缓存、DNS服务器
- 建立TCP连接:三次握手
- 发送HTTP请求:浏览器向服务器发起请求行、请求头、请求体
- 服务器处理请求并返回:返回响应状态码、响应头和响应体
- 浏览器解析渲染页面:如果请求的是HTML,就走渲染流程;如果请求的是接口数据,则交给JavaScript处理
这个流程里每一个环节都能单独拆出来深挖,比如DNS解析的细节、TCP三次握手为什么是三次、HTTP和HTTPS的区别、浏览器缓存策略等。所以这道题与其说是一道题,不如说是一个“母题”,能把这一整条链路相关的八股文全部串起来。
3.2 浏览器渲染流程与回流重绘
页面渲染的流程,也是面试官喜欢追着问的。核心过程是:解析HTML生成DOM树,解析CSS生成CSSOM树,两者合并生成渲染树(Render Tree),然后计算布局(Layout)和绘制(Paint)。
这里有个很容易被问到的点:**什么是渲染树?**渲染树并不是DOM树和CSSOM树简单相加,它只包含可见元素。比如设置了display: none的元素不会出现在渲染树里,但visibility: hidden的元素虽然不可见,仍然会占位,所以会出现在渲染树里。这个区别面试中经常考到。
然后是回流(Reflow)和重绘(Repaint)。回流是指元素的尺寸、位置、结构发生变化时需要重新计算布局;重绘是指元素样式改变但布局没变的重新绘制。回流必然导致重绘,但重绘不一定会回流。
一道高频题是“怎么减少回流和重绘”。常规答案包括:
- 使用
transform和opacity实现动画,因为这两个属性触发的是合成(Composite)阶段,不走回流重绘 - 批量修改DOM,比如用
DocumentFragment或者先把元素设置为display: none,修改完再显示 - 缓存布局信息,避免在循环里反复读取
offsetTop、offsetHeight等属性 - 尽量通过修改
className来一次性变更样式,而不是逐条修改style
但要记住,这些常规答案之外,面试官更想听的是你真正遇到过性能问题后的排查思路。所以回答的时候建议结合一个实际案例,比如你之前优化过一个列表渲染卡顿问题,怎么定位到是频繁回流,怎么通过批量处理解决的。项目里没做过就去找个小demo重新体会一下,这种实操感是背八股文背不出来的。
3.3 浏览器缓存:强缓存与协商缓存
浏览器缓存几乎是每场面试必问的,因为它是前端性能优化里最直接、见效最快的一环。先分清两类缓存:
- 强缓存:命中后直接使用本地缓存,不发请求。通过
Expires(绝对时间)和Cache-Control(相对时间)控制,其中Cache-Control优先级更高 - 协商缓存:本地有缓存但不确定是否过期,需要向服务器发请求确认。通过
Last-Modified / If-Modified-Since和ETag / If-None-Match控制,服务器返回304就用本地缓存,返回200就更新缓存
面试时经常被追问“强缓存和协商缓存怎么选”。我的理解是:强缓存适合长时间不变的静态资源,比如带hash指纹的JS、CSS文件;协商缓存适合需要时不时校验、但又不想每次全量下载的资源,比如接口返回的配置信息。
补充一个细节:请求头里的Cache-Control: no-cache并不等于不缓存,它意思是“每次使用前都要去服务器验证一下”,实际上走的是协商缓存逻辑。而no-store才是真正的不缓存。这两个概念极容易混淆,面试里说出来会很加分。
3.4 跨域问题:从同源策略到解决方案
跨域是前端面试里绕不开的坎。首先要理解为什么会有跨域限制——浏览器的同源策略,即协议、域名、端口三者必须一致,否则浏览器会拦截响应。这是浏览器出于安全考虑的机制,不是服务器主动屏蔽你。
常见的解决思路有这么几种:
- JSONP:利用
<script>标签不受同源限制的特性,只能支持GET请求,而且现在用得比较少了 - CORS:后端在响应头里设置
Access-Control-Allow-Origin等字段,这是目前最标准的方案 - 代理转发:开发环境用webpack的
devServer.proxy,生产环境用Nginx反向代理转发请求 - postMessage:主要用于
iframe和window.open打开的窗口之间的跨窗口通信
面试官如果问“CORS的简单请求和预检请求有什么区别”,很多人会卡住。简单请求是指方法为GET、POST、HEAD,且请求头只包含安全字段的请求,不需要预检;预检请求是浏览器先用OPTIONS方法去问服务器“允许我发这个请求吗”,拿到允许的响应后再发真实请求。触发预检的条件是:使用了PUT、DELETE等方法,或者自定义了请求头,或者Content-Type不是三种简单类型之一。
这块的难点是容易站在前端视角看问题,忽略了跨域其实是浏览器行为。把机制讲清楚,再列出你在实际项目中用过的方案,这题就稳了。
4. HTML与CSS:面试里的“送分题”与“暗坑”
4.1 语义化标签和盒模型
HTML和CSS在面试里占比不高,但偶尔会出现一些看似简单、实际暗藏杀机的题目。语义化标签是个高频点,面试官会问“为什么要用语义化标签”,标准答法是:利于SEO、增强可访问性、方便代码维护。
但更高级的答法是举一个具体的例子,比如一个页面里<h1>只应该出现一次,由<div>堆出来的“伪标题”在屏幕阅读器里完全读不出来,对盲人用户非常不友好。这种细节会让面试官觉得你是真的在意可访问性,而不是背概念。
盒模型有两个版本:标准盒模型(box-sizing: content-box)和怪异盒模型(box-sizing: border-box)。区别在于width的计算范围:标准盒模型的width只包含内容区,不包含padding和border;怪异盒模型的width包含content + padding + border。
实际开发中,我基本所有项目都会设置全局的box-sizing: border-box,因为这样在给元素设置宽度时心里有数,不会被多出来的padding撑破布局。这个细节你可以在面试中提到,会显得你有实际工程经验。
4.2 垂直水平居中的N种姿势
垂直水平居中面试中经常让现场写样式。别以为简单,越基础的问题越能看出平时的积累。至少有这些方案:
- 使用
flex布局:display: flex; justify-content: center; align-items: center; - 使用
grid布局:display: grid; place-items: center; - 绝对定位 +
transform:position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - 绝对定位 +
margin: auto:需要给元素设置固定宽高,然后top/left/right/bottom全为0,margin: auto
面试时建议先把flex方案写出来,因为这几乎是现在兼容性最好、最常用的方案。然后补充说明grid布局更简洁,但需要注意兼容性。最后再说说绝对定位方案的适用场景,比如弹窗层。这样答既能展现广度,又能体现你对不同方案的取舍思考。
4.3 Flex布局高频考点
Flex布局在CSS面试里出镜率极高,因为现在几乎没有不用Flex的页面了。核心考点是justify-content、align-items、flex-direction这几个属性的作用,但更深一层会问flex属性的简写含义。
flex: 1其实是flex-grow: 1; flex-shrink: 1; flex-basis: 0%的简写,意思是这个元素在主轴方向上按比例占据剩余空间。很多人只知道flex: 1能让元素自适应撑开,但被追问拆开后反而说不清楚。
面试官还喜欢让现场实现一个经典布局:左侧固定宽度200px,右侧自适应撑满剩余宽度。用Flex的话:
.container { display: flex; } .left { width: 200px; flex-shrink: 0; } .right { flex: 1; }关键点是给左侧加上flex-shrink: 0,防止容器宽度不足时左侧被压缩。这个细节是很多新手容易忽略的,在面试时主动提出来,会让面试官觉得你写代码时考虑过边界情况。
5. 初相遇避坑指南:常见错误与心态
5.1 背诵式回答为什么撑不过三连追问
我最开始复习八股文时,犯过一个典型错误:每个问题都能背出标准答案,但面试官只要多问一句“为什么”就露馅。比如他能背出“display: none和visibility: hidden的区别”,但问“哪个会触发回流”时就愣住了。
后来我总结出一个规律:面试官的三连问,其实是在顺着“是什么-为什么-怎么办”层层往下挖。你会背的是“是什么”这一层,但真正的分数落在“为什么”和“怎么办”上。
拿闭包举例:
- 是什么:函数记住并访问外部作用域变量的能力
- 为什么:因为函数定义时会把外部作用域链保存下来
- 怎么办:利用它做数据私有化、记忆化,但注意别造成内存泄漏
每一层都要准备一两个例子。这是八股文复习里性价比最高的方法,问题总量没变,但每个问题的深度加了,面试时能扛住的追问就会多出好几轮。
5.2 手写题翻车现场:这几道题考前必须练
初相遇这一集,至少有这几道手写题建议考前闭着眼睛都能写出来。
new的手写实现(前面给过代码)call / apply / bind的手写实现- 深拷贝(要考虑数组、对象、循环引用)
- 防抖和节流
- 数组去重和拍平
拿call的简单实现来说,核心思路是把你想要绑定的对象临时变成函数的一个属性来调用:
Function.prototype.myCall = function (context, ...args) { context = context ?? window; // 兼容 null/undefined const key = Symbol('fn'); // 用 Symbol 避免属性名冲突 context[key] = this; const result = context[key](...args); delete context[key]; return result; };这里用Symbol作为临时属性名是个加分项,一般面试者会直接用字符串,面试官追问“有没有想过属性名冲突怎么办”,你答“用Symbol”,就体现出了对细节的思考。手写题不只要写对,还要解释每一步的意图。
防抖和节流也是高频,我建议把这两个写在一张卡片上,考前最后一天专门过一遍。防抖是“在你停止触发后delay毫秒执行”,节流是“每隔delay毫秒执行一次”。很多公司会直接让你写实现,写不出来前面答得再好也会大打折扣。
5.3 外包、校招、社招的八股文侧重点不同
热词里提到“吉利外包前端面试”,说明有些朋友在准备外包岗位的面试。不同岗位的考察侧重点确实不一样,说点我观察到的规律。
外包岗位通常更看重能不能马上上手干活,所以面试的问题会更贴近业务使用场景,比如“Vue里如何父子组件通信”“接口请求封装怎么做的”,纯原理类的问题相对少一些,但HTML、CSS、JavaScript基础仍然会考。
校招则更看重潜力,基础知识占比很高,数据结构和算法也经常出现,因为应届生没有太多项目经验可聊,八股文就成了主要的筛选工具。社招正好相反,项目经验占了很大比重,八股文更多是“热身题”,用来在项目深挖之间穿插。
不管哪种岗位,前端基石部分都不能有硬伤。所以这个系列的内容对所有类型的面试都有用,只是你需要根据自己的目标岗位调整复习重心。
5.4 面试画风突变现场:如何把八股文聊成技术交流
最后一个常见问题是:准备了那么多八股文,结果面试官问的不是你准备的那些,或者问法和标准答案不太一样,一下就慌了。这个情况太常见了,因为面试官本身就是在不同的题目之间灵活切换的。
我的经验是,遇到没准备过的问题不要急着说“不会”,先尝试从自己熟悉的知识点往那个问题上靠。比如问“浏览器怎么处理一个很大的JSON数据会不会卡顿”,你可以从事件循环、渲染机制、Web Worker这些你熟悉的点切入,把自己知道的部分讲出来,边讲边理思路。就算最终没答到点子上,面试官也会看到你在思考,而不是直接放弃。
把八股文当成技术交流的素材,而不是考试的题库,这个心态转换很重要。面试官也是人,他更愿意跟一个能聊得来的人共事,而不是一个背题机器。
6. 初相遇自查清单
这一集的内容到这里就差不多了。按连续剧的惯例,最后留一份自查清单,你来对照检查自己有没有真正吃透这一集的内容:
- 能画清楚原型链的指向关系,能手写
new - 能讲清楚闭包的原理、作用和一个实际应用场景
- 能准确判断一段代码里的this指向,并说出依据
- 能排对微任务和宏任务混在一起的输出顺序
- 能按时间线完整讲出从输入URL到页面渲染的过程
- 能区分强缓存和协商缓存,并说明各自的适用场景
- 能说出回流和重绘的区别,并给出减少回流的方法
- 能手写
call、防抖、节流,并解释每一步的意图
每一项里,如果只是“看过”但讲不出来,建议回到对应的章节再啃一遍。学八股文没有捷径,但它也远没到靠死记硬背的程度。真正理解原理之后,你会发现自己写代码时也会更顺手,面试只是顺带的结果。
下一集,我们聊聊JavaScript更深的那些面试题——作用域链、立即执行函数、变量提升这些“初相遇之后”的故事。追剧不急,先把这一集的内容消化透,我们下集见。