前端面试基础全解:HTML/CSS、JS核心机制与经典面试题

发布时间:2026/8/29 21:37:23
前端面试基础全解:HTML/CSS、JS核心机制与经典面试题 1. 初相遇为什么前端面试总绕不开八股文先讲个真实的场景。我有个之前带过的实习生科班出身在学校跟着做了两年多项目Vue、React都用得挺溜打包、部署、联调样样顺手。他去面一家中厂上来先是两三道选择题——和的区别、let和var的区别这类他心想这不送分吗结果越往后越不对劲。面试官开始往深里问的隐式转换具体走哪几条规则let有没有变量提升暂时性死区到底是什么时候产生的他当场就愣住了回来跟我说这些东西我平时写代码根本用不到啊。这个场景你应该不陌生。说句公道话八股文被骂了这么多年确实有很多脱离实际的内容但如果你因此把前端基础当洪水猛兽那面试这道坎基本过不去。原因很简单面试官需要通过一套低成本、标准化的题目来快速判断你的水平而基础知识恰好是区分“会用”和“懂原理”的试金石。我理解的八股文说白了就是一套高频考点清单。它考的其实不是背诵能力而是你对核心技术概念的理解是否成体系。你答和的区别背一遍谁都会但如果你能解释清楚JS运行时是怎么处理隐式转换的面试官马上就知道你是真懂还是背的。这套“前端面试八股文连续剧”就是想用一套系统性的方式把这些高频考点逐个拆解清楚。标题叫“那年杏花微雨初相遇”意思很简单——把前端面试当成一场相遇第一次见面我们先把最基础、最常见的那些问题聊透。这也符合连续剧第一集的定位不讲花活儿先把地基夯实。这第一篇我按前端知识体系的几个大块展开HTML与CSS、JavaScript核心机制、经典面试题的完整推演思路。每一块都从面试官的视角出发告诉你他们为什么这么问以及什么样的回答能拿高分。2. HTML与CSS看似送分实际上是在考细节功力2.1 HTML语义化不只是“好看”这么简单面试官问HTML语义化很多人第一反应是有利于SEO、方便阅读代码。这没错但只答到这一层面试官会觉得你在背概念。真正的理解要从“语义化的价值链条”来讲。语义化的核心价值有几个层面。首先是可访问性使用header、nav、article、aside这些语义化标签屏幕阅读器可以准确识别页面结构视障用户能更顺畅地浏览内容。其次是SEO搜索引擎爬虫通过标签语义判断内容权重和页面结构一个结构清晰的文章页面比一堆div嵌套的页面更容易获得好的排名。第三是维护性语义化标签本身就是一种代码注释团队协作时其他人能快速理解每个区块的作用——看到nav就知道是导航看到article就知道是独立的内容块。再往深一层面试官可能还会追问HTML5新增了哪些语义化标签。这时候你得能按类别说出来结构类header、footer、section、article、nav、aside、文本类mark、time、figure、figcaption、交互类details、summary、dialog等。能分门别类地说比你零散地报名字要更像一个系统学过的人。还有个小细节值得提——label标签和表单控件的关联。这虽然是基础但很多人写表单的时候不写label的for属性而是把label包在input外面。两种写法触发点击区域的效果类似但for属性明显更规范这属于那种面试官可能顺手考一下、但绝不能答错的细节。2.2 CSS盒模型与BFC从“有什么区别”到“为什么这样设计”盒模型几乎是每场前端面试都跑不掉的问题。基本答案是标准盒模型content-box和怪异盒模型border-box的区别标准盒模型的width指内容区宽度怪异盒模型的width包含content、padding和border。用box-sizing切换。但面试官真正想听到的是你知不知道实际开发中更喜欢用哪种现有主流组件库、设计规范里为什么默认border-box原因其实很简单视觉直觉。设计师标注一个按钮宽度100px标配是希望这个按钮的总宽度是100px包括内边距和边框而不是加了padding之后实际占120px。用box-sizing: border-box你写多少宽度就是多少宽度内边距和边框自动向内压缩页面布局就不会因为改内边距而“撑爆”。这也是为什么很多团队在reset样式里直接写* { box-sizing: border-box; }。BFCBlock Formatting Context块级格式化上下文的问题通常紧接着盒模型出现。面试官会问什么是BFC怎么触发解决了什么问题一个容易理解但不失准确的解释是BFC是页面上一块独立的渲染区域区域内部元素的布局不会影响外部元素。常见的触发条件有float值非none、position为absolute或fixed、display为inline-block或flex或grid、overflow非visible等。BFC最经典的三个应用场景是清除浮动父元素创建BFC浮动子元素就不会溢出、防止外边距合并两个相邻元素的外边距取最大值而非相加创建独立BFC可阻断合并、自适应两栏布局左侧固定宽度右侧BFC自适应剩余空间。很多人在回答BFC时只背“什么是BFC”和“怎么触发”能把“解决什么问题”和“为什么能解决”讲清楚的人数量明显要少一截。2.3 层叠上下文与选择器优先级一道高频坑题CSS里还有一个高频考点是选择器优先级。大部分人能背出!important 内联样式 ID选择器 类/属性/伪类选择器 类型/伪元素选择器 通配符。但要小心这里面有坑。第一个坑是优先级是按“权重分值”计算的不是按“出现的先后顺序”。我用的计算方法是ID选择器计100分类选择器计10分类型选择器计1分内联样式计1000分。!important则是在所有常规声明之上但如果有两个!important仍然要按上述权重比较。第二个坑是同一个元素的同一属性可能命中多个选择器最终生效的是权重和最高者权重相同才轮到“就近原则”后面的覆盖前面的。层叠上下文是另一个经常被问到的概念而且比优先级更抽象。我的面试经验是面试官出的题目往往是把z-index和层叠上下文放一起考一个position: relative且z-index: 1的元素和一个position: absolute且z-index: 2的元素谁在上层如果其中一个元素内部嵌套了更低层的层叠上下文结果会发生什么变化回答这类问题你要先说出层叠上下文的触发条件z-index值非auto且position不是static、opacity小于1、transform值非none、filter值非none等。然后说出规则层叠上下文内部的层叠顺序是原子性的外部元素的z-index不会和内部元素逐层比较。理解了这两条这类题基本可以一网打尽。这里补充一个实操经验定位元素position值非static配合z-index超过10层的页面极容易出现层叠上下文嵌套混乱排查起来非常痛苦。我现在的做法是组件内部少用绝对定位叠加需要层级控制时优先用flex、grid的排列顺序或者用CSS变量统一管理z-index的层级档位比如--z-index-dropdown: 1000、--z-index-modal: 2000这种避免到处写魔法数字。3. JavaScript三条主线变量、闭包与原型链3.1 变量提升与暂时性死区不只是背结论要理解JS的编译过程var的变量提升和**let/const的暂时性死区**是JS面试的必问考点但很多人的理解停留在“var会提升let不会”这种粗糙的层面。要想回答得漂亮你得从JS引擎执行代码的底层机制说起。JS代码在执行前会先经过一个编译阶段这个阶段不会立即执行任何语句而是先进行作用域收集。引擎从上到下扫描代码遇到var声明就在当前作用域创建一个变量初始化为undefined这就是“变量提升”的源头。遇到function声明则直接把这个函数对象创建好这解释了为什么函数声明可以在定义之前调用。遇到let和const同样会提升这一点很多人理解错了但它们不会初始化而是在作用域里留下一个“未初始化”的标记直到执行到声明语句时才绑定值。在这个标记存在到初始化之间的区间访问变量引擎会直接抛ReferenceError这就是暂时性死区TDZ的由来。所以面试官如果问你“let到底提升不提升”可以回答提升的是作用域绑定但不触发初始化所以在声明之前访问会报错也就是TDZ。这样回答既承认了提升的底层机制又解释了为什么会报错比直接说“不提升”准确得多。一个经典变种题是var a 1; function foo(){ console.log(a); if(false){ var a 2; } } foo();输出什么答案是undefined因为var a在函数作用域内被提升局部变量a遮蔽了全局的a。这种题能考出你对作用域、提升机制的综合理解值得仔细琢磨。3.2 闭包它到底是什么、为什么需要、又会带来什么问题闭包这个话题每个前端都以为自己会但每次面试都能筛掉一批人。如果你只背“函数内部返回函数然后内部函数能访问外部函数的变量”那最多只能拿个及格分因为这只是现象。我认为面试官真正想听的是三层递进第一层闭包是什么。函数创建时它所在的词法作用域被保存下来形成一个闭包。当这个函数在其他地方被调用时依然可以通过作用域链访问那个被保存的词法环境中定义的变量而不是回到定义处去执行。第二层闭包解决了什么问题。在JavaScript中函数是一等公民可以作为参数传递、作为返回值返回。问题是函数在被延迟调用时它需要访问定义时环境中的变量。如果不做“闭包捕获”这个函数就无法在外部环境中正常工作。闭包的本质是让函数“带着环境”旅行。第三层闭包带来的问题是什么。最典型的是内存泄漏风险如果闭包引用了大对象而这个闭包又长期被全局变量持有那么被引用的对象永远不会被垃圾回收。另外在循环中使用闭包捕获循环变量特别是var时代的经典问题会因为共享同一个作用域而导致所有闭包看到同一个最终值。解决循环引用问题你得掌握几种方案用let替代varlet每次迭代创建一个新的词法环境、用立即执行函数IIFE传入参数拷贝、用Array.prototype.forEach之类的方法天然创建新作用域。面试时能把“为什么let能解决这个问题”说清楚——let在每次迭代时都会在循环体内部创建一个新的词法环境闭包捕获的是这个新的环境——那就说明你是真懂而不是背答案。3.3 原型链与new一张图讲清对象之间的联系原型链的面试题绕不开这几个__proto__是什么prototype是什么Object.create()有什么用new到底做了什么我的建议是直接用一段代码来理解function Person(name) { this.name name; } Person.prototype.sayName function() { console.log(this.name); }; const p new Person(Alice); p.sayName(); // Alice执行new Person(Alice)时引擎做了四件事创建一个新对象、把这个对象的__proto__指向Person.prototype、将构造函数内部的this绑定到新对象并执行构造逻辑、如果构造函数没有显式返回对象则返回这个新对象。所以当你写p.sayName()时JS先在p自身找sayName找不到就去p.__proto__也就是Person.prototype找这个方法就是这样被调用到的。这个“顺着__proto__一直向上找直到null”的链路就是原型链。链条的顶端是Object.prototype再往上就是null。这也是为什么所有对象都有toString()、hasOwnProperty()这些方法因为它们定义在Object.prototype上。面试中另一个高频追问是如何实现继承class语法实际上是一层语法糖底层还是基于原型链。如果你能熟练地用Object.create()实现原型式继承并用constructor指回正确构造函数面试官基本会点头。function Animal(name) { this.name name; } Animal.prototype.eat function() { console.log(${this.name} is eating); }; function Dog(name, breed) { Animal.call(this, name); this.breed breed; } Dog.prototype Object.create(Animal.prototype); Dog.prototype.constructor Dog; Dog.prototype.bark function() { console.log(${this.name} is barking); };这段代码里最容易被问到的两点为什么要用Object.create(Animal.prototype)而不是直接Dog.prototype Animal.prototype因为前者创建了一个新对象作为Dog.prototype修改它不会影响Animal.prototype后者直接引用同一个对象修改Dog.prototype会连带污染Animal.prototype。为什么要修复constructor因为Object.create(Animal.prototype)产出的对象的constructor属性指向Animal而不是Dog如果不修复new Dog()的实例通过constructor找构造函数时会找到错误的值。3.4 事件循环Event Loop从一道题看完整执行顺序事件循环现在几乎成为前端面试的分水岭。面试官会用一道类似下面的题来区分背题的人和真懂的人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。你要解释清楚的原因有两个第一JS是单线程的它维护一个调用栈同步代码进入调用栈依次执行第二异步任务在完成回调后进入任务队列/微任务队列只有当调用栈清空之后事件循环才会把队列中的任务再次推入调用栈执行。这里的关键区分是宏任务和微任务。我习惯用一个生活场景来理解宏任务就像不同楼层的电梯呼梯微任务则是电梯内部按下的楼层按钮。电梯每次停在一个楼层宏任务先把这层所有要上的人微任务处理完再去响应新的呼梯下一个宏任务。这样理解下来微任务永远会先于下一个宏任务执行这一点无论如何都不能答错。常见的宏任务有setTimeout、setInterval、setImmediateNode环境、requestAnimationFrame浏览器渲染帧相关在事件循环中位置比较特殊、I/O操作。常见的微任务有Promise.then、queueMicrotask、MutationObserver浏览器环境、process.nextTickNode环境优先级比普通微任务还要高。Node环境的事件循环和浏览器不同还额外有timers、pending callbacks、idle/prepare、poll、check、close callbacks这几个阶段。面试时如果对方问Node的事件循环与浏览器有何区别建议先明确你了解的是哪个版本的Node事件循环模型Node 11之后process.nextTick与微任务的执行顺序有变化这一句话就能体现出你的知识时效性。4. 经典面试题全链路拆解从URL输入到页面展示4.1 为什么这道题是面试题的“万能钥匙”这道题几乎每个前端都准备过但它好就好在覆盖面极广。从网络协议到浏览器渲染机制从缓存到性能优化从JS执行到CSS布局每一个环节都可以单独拿出来深挖。很多面试官会把这道题作为开胃菜然后顺着你的回答不断追问追问到的深度就决定了你的最终评级。我建议的回答框架分五个阶段DNS解析、TCP连接、HTTP请求与响应、浏览器解析与渲染、资源加载完成后的交互阶段。每个阶段你都要能说出关键细节并且要有能力在面试官追问时继续往深走。4.2 五个阶段的细节与追问点第一阶段DNS解析。输入URL之后浏览器先在本地缓存查找DNS记录找不到就向系统配置的DNS服务器发起递归查询。对前端来说这一阶段最值得关注的知识点是DNS预解析link reldns-prefetch和CDN背后的DNS智能调度这两点和你后续的性能优化方案是相通的。第二阶段TCP连接。这里你需要提到三次握手客户端发送SYN包、服务端回复SYNACK包、客户端发送ACK包确认。三次握手的目的是确保双方的收发能力都正常。如果页面是HTTPS还要多一层TLS握手握手过程中包含证书校验、密钥协商等过程。一次完整的HTTPS握手会额外消耗至少一个RTT这也是为什么性能优化里会提到“减少RTT”和“TLS 1.3的0-RTT恢复”。第三阶段HTTP请求与响应。面试官经常会在这里追问HTTP/1.1和HTTP/2的区别。关键区别至少要说全HTTP/1.1的队头阻塞问题、HTTP/2的多路复用一个连接并发处理多个请求、头部压缩HPACK、二进制分帧层、服务端推送。再往深一点HTTP/2的多路复用虽然解决了HTTP层的队头阻塞但TCP层的队头阻塞依然存在这也是HTTP/3转用UDPQUIC的核心原因。能把这一层说出来面试官会认为你对网络协议有全景认知。第四阶段浏览器解析与渲染。这是整道题的核心环节也是细节最多的环节。浏览器拿到HTML后开始词法分析和语法分析构建DOM树同时解析CSS构建CSSOM树然后合并生成渲染树RenderTree。这里有一个关键点渲染树只包含可见节点display:none的节点不会被渲染但visibility:hidden的节点虽然不可见但占据布局空间因此也存在于渲染树中。接下来是布局Layout/Reflow和绘制Paint再到合成Composite。很多人在这个环节卡在“CSS和JS的阻塞关系”。标准回答是CSS会阻塞渲染因为渲染树依赖完整的CSSOMscript标签无async/defer会阻塞DOM解析因为JS可能修改DOM。把script放在body底部、或者使用defer就是为了避免这种阻塞。用async加载的脚本下载时不会阻塞DOM解析但执行的时候仍然会阻塞渲染。第五阶段交互阶段。页面展示完成后用户开始点击、滚动、输入事件触发与JS执行开始交互。这个阶段会关联到事件委托、防抖节流、requestAnimationFrame、重排与重绘等考点。这已经进入更广的范畴了面试时通常是顺着项目经验继续聊到这些点。4.3 常见追问缓存策略、渲染性能与优化手段在聊完五个阶段之后面试官大概率会在“HTTP请求与响应”或“浏览器解析与渲染”环节顺势追问缓存。这个考点太常出现建议作为第二层准备的核心内容。强缓存通过Cache-Control和Expires控制浏览器在缓存有效期内不会发出请求。协商缓存通过ETag和Last-Modified进行判断每次请求都会发送条件请求由服务器判断是否返回304。我建议在回答里补充一个实际经验Cache-Control的max-age和s-maxage的区别以及immutable指令的用途。后者用在前端静态资源上非常合适——带哈希指纹的静态资源如app.a1b2c3.js应该设置max-age31536000, immutable而不带哈希的HTML页面应该设置no-cache。这种细节很难靠背题答出来往往是真正做过优化的人才能提出的。接着是渲染性能优化。Layout布局和Paint绘制是最耗时的渲染阶段优化方向包括减少DOM操作批量更新、避免强制同步布局比如在循环中读取offsetHeight再修改样式、用transform代替top/left做动画transform不触发布局只触发合成、使用will-change提示浏览器提前优化、考虑使用content-visibility跳过屏外内容渲染等。回答这些优化时务必说明原理——面试官关心的是你知不知道“为什么这样优化有效”。5. 前端面试的持续性从“初相遇”到“长线养成”5.1 把八股文当做索引而不是答案库经过前面几个大块的梳理你应该已经发现八股文的作用其实不是替你把所有知识“灌”进脑子而是给你提供一张知识地图的索引。比如的隐式转换规则本身是八股但顺着它往下走你就会触达ToPrimitive、类型转换、抽象相等比较这些更深的内容。再看闭包往下走你会触达作用域链、垃圾回收、模块模式。这些延伸才是真正的能力。所以我的建议是不要背八股文要“追”八股文。每看到一个考点先问自己三个问题这个概念解决什么核心问题它底层依赖什么机制如果在项目中设计一个类似场景我会怎么用它三个问题想明白了这个概念才算真正变成你的。拿经典的“为什么0.1 0.2 ! 0.3”举例。直接背答案是“浮点数精度损失”但它背后是IEEE 754的双精度浮点表示法是二进制无法精确表示十进制小数是尾数位和舍入规则。如果你顺着这条线继续看会发现这不仅是面试题更是实际开发中处理金额计算时的决策依据——是使用toFixed还是引入decimal.js取舍标准是什么。5.2 面试答题的节奏与“钩子”设计我在帮朋友做模拟面试时发现很多技术扎实的人面试分数不高原因是答题节奏没掌握好。面试不是笔试不是把标准答案一字不落说出来就能拿满分的。好的面试回答应该是“结构化有钩子”。结构化的意思是先给结论再展开原因最后补充例子。比如问“什么是闭包”你先说“闭包是函数与其词法作用域的组合让它能访问定义时的变量环境”然后说“它的价值是支持函数一等公民的特性实现数据封装和模块化”最后举一个防抖函数或者计数器工厂的例子。这种“总—分—例”三段式结构面试官很容易跟上你的思路。“钩子”是你主动抛出的、引导面试官往你擅长的方向追问的信息点。比如你在介绍项目时提了一句“这个项目里我们用Web Worker处理了大文件分片上传期间还踩过内存占用的坑”面试官大概率会顺着往Web Worker、文件上传、内存优化这些方向问。只要你对这些点做过深度准备整个面试节奏就能往你熟悉的方向倾斜。这是一个被低估的面试策略值得刻意练习。5.3 这一系列还会聊什么既然标题叫“连续剧”第一篇的内容只是序幕。后续我计划按以下几期往下展开第一期聊完基础三件套之后第二期会聚焦JavaScript进阶专题包括异步编程模式的演进回调、Promise、async/await、事件循环在Node和浏览器中的差异对比、深拷贝的各种实现细节与边界分析第三期聊框架方向以Vue和React的取舍与核心原理为主包括响应式系统的实现思路、虚拟DOM与diff算法、hooks设计背后的动机等第四期进入工程化领域聊模块化规范演变、打包工具的核心机制、依赖管理、构建提速第五期再聊性能优化、浏览器渲染原理、前端安全等实战向的内容。这些话题之间是有递进关系的先把语言特性和浏览器原理打牢再进入框架和工程化理解深度会完全不一样。我也计划在系列过程中结合真实面试中的高频追问整理一份“面试问答分层清单”——同一道题按初级、中级、高级分别展现出什么样的回答水平这样对照练习的效果会更好。“那年杏花微雨初相遇”用这个标题来开场是希望你把前端面试备考看作一次持续的学习旅程而不是最后关头的冲刺。第一篇讲的这些基础内容很多好像简单但能系统地讲清楚、能经得起追问的人并不算多。这一篇算是个起步后面还有更多值得一起拆解的话题。