
1. HTML/CSS那些你以为会了、一问就卡壳的底层考点前端八股文刷了几轮之后我发现一个规律越基础的东西越容易在面试里翻车。尤其是 HTML 和 CSS平时写业务代码都是复制粘贴真到了让你手写布局、解释渲染原理的时候脑子里的知识点全是一团浆糊。这一章先把最容易被追问的底层细节捋清楚。1.1 语义化标签、盒模型与 BFC两道必问题先说语义化。面试官问语义化不是让你背“header、nav、main、article、aside、footer”这几个标签而是想听你说清楚为什么需要语义化。我自己的理解是三层第一层是给浏览器和搜索引擎看的方便爬虫识别页面结构和重点内容直接关系到 SEO第二层是给屏幕阅读器等辅助工具看的视障用户依赖标签结构来跳转和朗读第三层是给维护代码的人看的语义化的结构天然就是一份文档新人接手项目不用猜这个 div 到底是导航还是页脚。实际项目里最容易被忽略的是h1标签的使用层级一个页面只保留一个核心h1其余的标题层级严格按照h2到h6递减这对 SEO 的影响比很多人想象中大得多。盒模型这块面试官一般会直接甩给你一道代码题一个元素设置width: 200px; padding: 20px; border: 5px solid #000;问它的实际占位宽度是多少。如果是标准盒模型box-sizing: content-box实际宽度是200 20*2 5*2 250px如果是 IE 盒模型box-sizing: border-box实际宽度就是200px内容区会被压缩到150px。这个计算一定要张口就来不能犹豫。我习惯全局设置* { box-sizing: border-box; }不然布局调起来真的很痛苦尤其是做响应式的时候padding 一加就把容器撑爆了。BFC块级格式化上下文是 CSS 面试里永远绕不开的硬骨头。面试官问 BFC本质上是在考察你对 CSS 视觉格式化模型的理解。我总结了一套通俗的讲解方式BFC 可以理解成页面里的一个“独立结界”结界内部的布局不受外部影响外部也进不来。触发 BFC 的常见方式有float非 none、position为 absolute 或 fixed、display: inline-block或flex或grid、overflow非 visible、contain: layout等。BFC 在实际开发中主要有三个用途清除浮动父容器触发 BFC 后可以包住浮动的子元素、防止 margin 塌陷兄弟元素或父子元素的margin不再重叠传递、阻止元素被浮动元素覆盖。比如经典的.clearfix方案本质上就是利用overflow: hidden或display: flow-root让父容器生成 BFC。display: flow-root是我现在比较推荐的方式语义清晰又不会像overflow: hidden那样意外裁剪掉子元素的内容。1.2 层叠上下文、flex 布局与居中大法高频实战题层叠上下文stacking context这道题很多工作了三四年的前端也答不完整。面试官通常会给你一段多层嵌套的 HTML里面设置了一堆z-index问你哪个元素在最上面。这种题目的核心规则其实就一条同一层叠上下文内比较 z-index 数值大小不同层叠上下文之间先比较父级层叠上下文的层级父级高的永远在上面。触发层叠上下文的方式很多除了z-index不为 auto 的定位元素opacity小于 1、transform非 none、filter非 none、will-change指定了这些属性、mix-blend-mode非 normal 等等都会触发。这里的坑在于很多人以为只有定位元素才参与层叠比较实际上transform动画元素也会触发层叠上下文导致动画元素莫名遮挡了相邻的定位元素。排查这类问题时我的习惯是打开 DevTools找到对应元素看 Computed 面板里是否有“Stacking context”的标记然后逐层往上查父级很快就能定位问题。flex 布局算是 CSS 里最常用的布局方案了但能讲清楚flex: 1到底做了什么的人不多。flex是flex-grow、flex-shrink、flex-basis三个属性的缩写。flex: 1等价于flex: 1 1 0%意思是如果容器有剩余空间按比例放大如果空间不足按比例缩小基准尺寸为 0%也就是完全按照剩余空间来分配。flex: auto等价于flex: 1 1 auto基准尺寸是内容本身的大小。还有一个容易被忽略的属性是flex-shrink默认值是 1当容器宽度不足时子项会被压缩如果你不希望某个元素被压缩要显式设置flex-shrink: 0。这些细节在移动端适配和表格类布局里特别容易踩坑比如一个长文本的标题在 flex 容器里被压缩成了省略号但相邻的按钮没有被压结果标题宽度算错、按钮位置跑偏。水平垂直居中这道题也是面试常客我总结一套“背诵版”不定宽高的弹性元素用display: flexjustify-content: centeralign-items: center定宽高的元素用position: absolutetop: 50%; left: 50%transform: translate(-50%, -50%)如果已知宽高可以用position: absolute 四边为 0 margin: auto单行文本用text-align: centerline-height等于容器高度。这里我特别想强调一个细节margin: auto方式实现居中前提是元素必须设置了具体的宽高不然 auto 的上下外边距是 0垂直方向就失效了。2. JavaScript 核心机制原型链、闭包、this 与事件循环JS 八股文是整个面试的大头几乎每一个知识点都可能被深挖三轮。我见过太多候选人把“原型链”背得滚瓜烂熟但画图的时候就画歪了。这一章把几个高频重灾区挨个过一遍。2.1 原型链与继承纸上谈兵容易画图说话难原型链这道题我觉得最好的准备方式就是把图刻在脑子里实例.__proto__ 构造函数.prototype构造函数.prototype.__proto__ Object.prototypeObject.prototype.__proto__ null。真正容易出问题的地方是函数对象这条线构造函数.__proto__ Function.prototype因为构造函数本身也是对象它是由Function创建出来的。很多人在画图的时候把Person.__proto__画成了Person.prototype这一步错了整张图就废了。继承方式的演进也是必考内容。我记得从 ES5 的原型链继承、构造函数继承、组合继承到 ES6 的class extends每一代的优缺点都要能说清楚。原型链继承的缺点是多个实例会共享同一个原型上的引用类型属性比如this.arr []如果在原型上定义实例之间的arr是同一个引用改一个全变。构造函数继承的缺点是方法都在构造函数里定义每次实例化都会创建一份函数副本无法复用。组合继承是前两者的结合但会调用两次父类构造函数造成子类实例上存在一份多余的父类属性。class extends本质上还是基于原型的语法糖但内部处理了严格模式和静态继承等细节。面试进阶题还会追一句extends的方法它到底是怎么工作的class Child extends Parent时Child.__proto__ Parent这叫静态继承类本身继承父类的静态方法Child.prototype.__proto__ Parent.prototype实例继承父类原型上的方法。这两条线缺一不可Object.setPrototypeOf在这里的作用就是用两条线同时继承。2.2 闭包、this 指向与 apply/call/bind闭包这道题面试官的套路是“说说闭包是什么工作中哪里用到过”。闭包的定义本身很简单一个函数能够访问其外部作用域中变量的一种机制即使外部函数已经执行完毕。但真正的难点在于理解闭包的形成原理——每个函数定义时就确定了它的作用域链执行时通过作用域链查找变量。我工作中用得比较多的场景是防抖节流函数、函数柯里化、缓存工具、封装私有变量。防抖节流也是高频手写题。防抖debounce的意思是在事件被连续触发时只有最后一次触发后的等待时间内没有再触发才执行函数。节流throttle的意思是在一定时间间隔内只执行一次函数。我常用的防抖写法是function debounce(fn, delay 300) { let timer null; return function (...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; }这里注意两点第一返回的函数内部要用apply把this绑定到调用者上否则原函数的this会丢失第二定时器要在公共闭包环境里每次触发都先清除前一个定时器这样才能保证“最后一次触发才执行”。this 指向问题我的理解方式很简单this 是在函数被调用时确定的谁调用了它this 就指向谁。全局调用指向window严格模式下是undefined对象方法调用指向这个对象new调用指向新创建的实例。箭头函数没有自己的 this它会捕获定义时的外层 this。apply、call、bind都是用来显式指定 this 的区别是call接受参数列表、apply接受参数数组、bind返回一个新的绑定函数且不会立即执行。2.3 事件循环与微任务/宏任务异步编程的底层逻辑JS 是单线程语言但为什么能处理大量并发请求答案就是事件循环Event Loop。事件循环的核心流程执行一个宏任务比如一段脚本执行过程中遇到微任务就把它排入微任务队列遇到宏任务如 setTimeout、setInterval、I/O就把它排入宏任务队列当前宏任务执行完毕后清空整个微任务队列然后从宏任务队列中取出下一个宏任务执行如此循环。关于微任务的来源除了Promise.then/catch/finally还有queueMicrotask、MutationObserver、process.nextTickNode 环境。宏任务包括整体脚本、setTimeout、setInterval、setImmediate、I/O、UI 渲染等。这里有个容易混淆的地方Promise构造函数里的执行器是同步执行的只有then回调才是微任务。比如console.log(start); Promise.resolve().then(() console.log(promise)); setTimeout(() console.log(timeout), 0); console.log(end); // 输出顺序start - end - promise - timeout第一轮宏任务整段脚本同步打印 start 和 end然后把 promise 微任务排入队列setTimeout虽然延迟是 0但它属于宏任务当前宏任务结束后先清空微任务再执行下一个宏任务。这个输出顺序一定要熟练面试时临场画一个时间线图加分效果很明显的。关于事件循环还有一个进阶考点async/await的执行顺序。await右边的表达式会立即执行但await后面的代码会被当作微任务延后执行。比如async function foo() { console.log(foo start); await bar(); console.log(foo end); } async function bar() { console.log(bar); } foo(); console.log(main); // 输出foo start - bar - main - foo endawait bar()先执行bar()bar内部同步输出 bar然后await把继续执行的权利让渡给主线程所以输出 main 之后才回到foo()内部输出 foo end。3. 浏览器与网络从页面渲染到后端通信的完整链路前端面试的另一大板块是浏览器工作原理和网络协议。这块内容多而杂但面试官问来问去也就那几个方向从输入 URL 到页面展示发生了什么、浏览器渲染流水线、HTTP 缓存、跨域方案、HTTPS 握手。一次把它吃透以后面试都稳了。3.1 从 URL 输入到页面渲染一条龙流程串讲这道题可以说是前端八股文的“总纲”面试官期待你能把浏览器的工作流程从头到尾串起来。我的标准回答分五个阶段阶段一URL 解析与 DNS 查询。浏览器先解析输入的内容判断是搜索关键字还是合法 URL。如果是合法 URL就开始 DNS 解析把域名解析成 IP 地址。DNS 解析的顺序是浏览器缓存、系统缓存hosts 文件、路由器缓存、本地 DNS 服务器、根 DNS 服务器逐级递归查询。这里可以提一下 DNS 缓存和预解析dns-prefetch对性能的影响。阶段二建立 TCP 连接。经过三次握手建立可靠的传输通道。面试官如果追问“为什么是三次”回答是确保双方的发送和接收能力都正常同时同步初始序列号防止历史重复连接初始化造成混乱。阶段三发送 HTTP 请求并接收响应。浏览器向服务器发送 HTTP 请求报文服务器返回响应报文包含状态码、响应头、响应体。这里可以提一下网络分层模型和 HTTP 的发展历史HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3以及队头阻塞问题从 HTTP/1.1 的并发连接数限制到 HTTP/2 的多路复用再到 HTTP/3 基于 UDP 的 QUIC 协议的演进。阶段四浏览器解析并渲染页面。这是一个复杂的过程下面单独展开。阶段五连接结束。如果Connection: keep-aliveHTTP/1.1 默认开启连接会保持一段时间以便复用否则经过四次挥手关闭连接。3.2 渲染流水线、回流与重绘优化页面性能的基础浏览器渲染页面经历的过程解析 HTML 生成 DOM 树、解析 CSS 生成 CSSOM 树、合并两棵树生成布局树Layout Tree、分层并绘制Paint、合成Composite并显示到屏幕上。要特别注意一个细节JS 的解析会阻塞 DOM 的构建因为 JS 可能通过document.write修改文档结构。所以现代页面普遍把script标签放在 body 底部或者使用defer延迟执行但保证 DOM 解析完成后执行和async异步加载谁先到谁先执行不保证顺序。回流reflow是指当元素的几何属性宽高、位置、字体大小等发生变化时浏览器需要重新计算布局树。重绘repaint是指元素的外观发生改变颜色、背景、可见性等但几何属性没变浏览器只重新绘制而不重新计算布局。回流的代价远大于重绘。我总结的减少回流实战技巧批量修改 DOM 样式用class切换代替逐个style修改避免在循环中读取offsetWidth等强制同步布局的属性用requestAnimationFrame批量处理使用transform做动画代替top/left变化因为transform不触发回流而是走合成线程对复杂动画元素使用will-change: transform提前告知浏览器创建合成层。3.3 HTTP 缓存与跨域解决方案网络面试的两个主力HTTP 缓存是必考题我建议用一张表来记忆缓存类型相关响应头触发条件是否发请求强缓存Cache-Control: max-age3600未过期否强缓存ExpiresHTTP/1.0未过期否协商缓存ETag/If-None-Match强缓存失效是协商缓存Last-Modified/If-Modified-Since强缓存失效是实战中推荐使用Cache-Control来设置强缓存因为Expires依赖客户端时间用户改了系统时间就不可靠了。协商缓存中ETag比Last-Modified更精确因为Last-Modified只能精确到秒且可能被某些代理服务器修改。强缓存命中返回 200from disk cache / from memory cache协商缓存命中返回 304。跨域问题要区分几种场景同源策略是浏览器的安全机制只有协议、域名、端口都相同才叫同源。跨域解决方案里CORS跨域资源共享是主流方案需要服务器设置Access-Control-Allow-Origin等响应头JSONP利用script标签不受同源限制的原理只支持 GET 请求适合老项目对接postMessage适合 iframe 和 window 之间的跨窗口通信WebSocket本身就是跨域友好的协议本地开发环境常用 Vite 或 webpack-dev-server 的代理方案来绕过跨域。如果要手写一个 JSONP实现思路是动态创建script标签src指向接口地址并带上一个回调函数名参数后端返回callbackName(data)这种格式的 JS 代码浏览器加载并执行回调。实际工作里我基本用fetch CORSJSONP 只用来处理一些老后端接口。4. 框架面试题Vue 与 React 的双雄对决框架部分在面经里占比越来越大Vue 和 React 的对比题几乎是必考。这一章把双方的核心机制、生命周期、数据流、性能优化方式摆在一起讲透也会穿插一些热词里出现过的 Vue 相关的工程化内容。4.1 Vue 响应式原理从 Object.defineProperty 到 ProxyVue 2 的响应式是基于Object.defineProperty实现的Vue 3 的核心改成了Proxy。面试官问你“Vue3 为什么改用 Proxy”你要能提到三个关键原因第一Object.defineProperty只能监听对象的已有属性新增和删除属性都检测不到所以 Vue 2 才必须提供Vue.set和Vue.delete这种 API第二它无法监听数组的索引变化和length变化Vue 2 只能通过重写数组的 7 个方法push、pop、shift、unshift、splice、sort、reverse来变相实现第三Object.defineProperty需要递归遍历对象的每个属性设置 getter/setter对象层级越深初始化递归开销越大而Proxy可以拦截整个对象天然支持动态新增属性性能也更好。Vue 3 的响应式原理大致是reactive函数通过Proxy拦截get收集依赖和set触发更新effect函数负责注册副作用依赖关系通过WeakMap存储。当组件渲染时组件的 render effect 会读取响应式数据从而把自己收集为依赖当数据变化时set拦截器会触发对应的 effects重新渲染组件。这里有一个变量ref也是 Vue 3 的核心 API它内部用ref.value这种包装对象来实现基础类型数据的响应式本质上是class RefImpl里对value属性的 getter/setter 操作。4.2 虚拟 DOM 与 diff 算法框架性能的底层支撑虚拟 DOM 是 Vue 和 React 都使用的技术。为什么要用虚拟 DOM核心目的是将多次对 DOM 的操作合并为一次真正的 DOM 操作并且通过 diff 算法最小化实际操作的范围。真实 DOM 的操作代价很高而 JavaScript 层面的对象对比成本低得多。虚拟 DOM 的 diff 流程可以总结为同层对比、深度优先、逐层递归。Vue 2 的 diff 算法做了四个方向的优化头头对比、尾尾对比、头尾对比、尾头对比如果没有命中就通过 key 建索引查找。Vue 3 的 diff 算法在 Vue 2 的基础上增加了静态标记patchFlags和动态属性追踪编译阶段就能知道每个节点的哪些属性是动态的diff 时只对比标记了动态属性的节点。React 的 diff 算法也有自己的思路基于 key 进行最小化移动操作当列表插入或删除元素时key 可以帮助 React 精确定位哪些节点是新增、哪些是被移走的。这里面试官喜欢追问一个实战话题为什么不建议用 index 作为 key因为用 index 做 key 时如果列表发生插入、删除、排序等操作原本的 key 和元素对应关系会被打乱导致组件复用错误。比如一个列表第一项是输入框用户输入内容后你在列表头部插入了一条新数据index 全部后移了React/Vue 会复用原有的组件实例输入框里的内容就错位了。用唯一的 id 做 key 可以避免这个问题。这个点我在实际项目里踩过很多次建议写列表时首选 id没有 id 就生成一个稳定的唯一标识。4.3 Vue 生命周期、React Hooks 与关键差异Vue 3 的生命周期和 Vue 2 相比变化主要体现在组合式 APIComposition API上beforeCreate和created被setup替代setup在组件实例创建之前执行还没有thismounted对应onMountedbeforeUnmount对应onBeforeUnmount等。生命周期钩子在面试中的作用不只是背诵一个顺序表更重要的是解释“为什么要在这个时机做这件事”。比如数据请求应该放在onMounted里进行因为此时 DOM 已经挂载ref模板引用才能正确拿到 DOM 节点而setup阶段还没有 DOM所以无法访问模板引用的目标。React Hooks 的规则是另一大考点只能在函数组件顶层调用 Hooks不能放在条件语句、循环或嵌套函数里。原因在于 React 依赖调用顺序来维护 Hook 对应的状态队列如果某次渲染中 Hook 的调用顺序发生了变化状态就会错乱。useEffect的依赖数组也有讲究空数组表示只在挂载和卸载时执行不传数组表示每次渲染都执行传入具体依赖则只有依赖变化时才执行并且执行前会先执行上一次的清理函数。Vue 和 React 的差异我从三个角度总结响应式模型上Vue 的依赖追踪是自动的数据变化自动更新视图React 需要手动调用setState触发更新。模板语法上Vue 有v-if、v-for等专属指令模板是html风格的React 使用 JSX本质是 JavaScript 表达式更灵活但需要babel编译。状态管理上Vue 推荐 PiniaVuex 的升级版React 生态则提供了 Redux、Zustand、Recoil 等众多选择。补充一个热词里反复出现的点微前端。微前端架构就是把一个大应用拆成若干可以独立开发、部署的小应用运行时再由主应用统一加载和组合。业界比较成熟的方案有 qiankun基于 single-spa和 module federationWebpack 5 内置。主应用通过registerMicroApps注册子应用子应用暴露bootstrap、mount、unmount生命周期钩子。微前端的主要难点在于样式隔离、JS 沙箱和应用通信面试中被问到的时候能说出这几个痛点和对应解决方案就很加分。5. 前端工程化、构建工具与性能优化聊完框架就到了前端工程化和性能优化环节。这一部分面试官更看重你有没有真实项目经验而不是单纯背诵概念。工程化方向的高频考点包括 Vite 和 Webpack 的对比、模块化方案、代码分割、微前端架构、性能监控和 SEO 优化。这部分我也把热词里提到的 worker 上传、H264 解码这类偏实战的内容一起带出来讲。5.1 Vite vs Webpack构建工具的选型思考Vite 逐渐成为新项目的主流选择核心优势是基于原生 ES Module 的开发服务器。开发环境下Vite 按需编译浏览器请求哪个模块就转换哪个模块启动速度是秒级的而 Webpack 需要从入口开始递归构建整个依赖图把所有模块打包成 bundle项目越大启动越慢。Vite 的底层依赖了 esbuild一个 Go 编写的极速打包器来做依赖预构建同时支持HMR热更新时毫秒级生效。Webpack 的优势在于生态成熟、灵活性极高。生产构建时Webpack 通过大量的 loader 和 plugin 做代码压缩、Tree Shaking、代码分割、资源内联等优化。Vite 生产构建默认使用 Rollup也能支持manualChunks做分包策略。实际项目里如果是一个纯前端 SPA 项目用 Vite 体验会好非常多如果是复杂的老项目需要兼容大量自定义构建逻辑或者依赖了某个 Webpack 特有的插件那就先别急着迁移。面试时你可以说新项目我首选 Vite老项目维护继续用 Webpack这个回答既显示了你对工具差异的理解又贴合实际。前端工程化里还有一个高频题什么是 Tree ShakingTree Shaking 依赖于 ES Module 的静态结构在编译阶段就能确定哪些导出被使用、哪些没有被使用最终将未使用的代码从打包产物中移除。使用 Tree Shaking 的前提是代码必须用 ESM 语法import/export不能是 CommonJSrequire/module.exports且副作用sideEffects要在package.json中声明为 false否则打包器不敢随意删除代码。我遇到过一个真实的坑某个工具库的package.json里没有声明sideEffects字段导致 Tree Shaking 失效整个库被打进了 bundle首屏体积多了 200KB。排查半天才发现是这个字段缺失后来在工程规范里明确要求所有内部库都要声明sideEffects: false。5.2 首屏性能优化从指标定义到落地手段性能优化题目的回答逻辑我建议从“指标 → 手段 → 验证”三个层面展开。指标方面核心 Web Vitals 包括LCPLargest Contentful Paint最大内容绘制、INPInteraction to Next Paint交互到下一帧的延迟2024 年起替代 FID、CLSCumulative Layout Shift累计布局偏移。LCP衡量首屏加载体验CLS衡量视觉稳定性。面试时能说出来“LCP 应低于 2.5 秒CLS 应低于 0.1INP 应低于 200ms”这些基准值就是加分项。手段方面我按环节分类整理资源加载阶段使用 CDN 分发静态资源HTTP 强缓存 协商缓存组合开启 Gzip/Brotli 压缩图片使用WebP/AVIF格式并按需裁剪路由级代码分割按需加载组件preload提前加载关键资源prefetch预加载后续路由资源。渲染阶段服务端渲染SSR或静态站点生成SSG避免白屏关键 CSS 内联非关键 CSS 异步加载使用content-visibility: auto跳过屏幕外内容的渲染对长列表使用虚拟滚动。运行时使用 Web Worker 处理耗时计算任务避免阻塞主线程防抖节流减少高频事件处理合理使用requestAnimationFrame控制动画。网络阶段接口请求合并BFF 网关层聚合数据缓存使用 HTTP/2 多路复用WebSocket 替代轮询。热词里出现了一个非常典型的实战场景前端使用 Worker 上传大文件。这个场景我做过思路是通过 Worker 读取文件分片File API 的sliceWorker 内计算每个分片的哈希比如用spark-md5再并发上传分片到服务器服务端按分片编号合并。这样做的目的有三个分片上传可以避免大文件一次性上传超时或失败重传整个文件Worker 计算哈希不阻塞主线程页面交互不卡顿同时支持断点续传记录已上传分片下次只传缺失部分。实现时要注意分片大小一般在 2MB~5MB 比较合适太大会失去分片的意义太小则请求数过多导致服务端压力大。计算哈希时可以用crypto.subtle.digest或spark-md5前者是浏览器原生异步 API后者是纯 JS 库需要根据兼容性选型。再补充热词里提到的一个冷门但有意思的点前端 JS 解码 H264。在我做过的一个监控类项目里后端推流的是 H264 裸流前端无法直接用video标签播放需要在浏览器里解码。当时的方案是使用Broadway一个基于 Emscripten 编译到 WASM 的 H264 解码器通过Web Worker接收二进制数据解码成 YUV 帧后绘制到 Canvas 上。这个方案的核心难点在于内存拷贝和数据格式转换YUV 转 RGB 需要逐像素处理性能要求很高。这类问题面试官出题概率不算高但如果简历里写了“视频处理”“Web Worker”相关经验提前准备好这个案例会非常加分。5.3 前端 SEO让搜索引擎看懂你的 SPA热词里“前端 SEO”出现的频率也很高。SPA单页应用对 SEO 天然不友好因为搜索引擎爬虫抓取到的 HTML 往往是只有一个根节点的空壳页面内容全靠 JS 渲染。针对这个问题的常规方案有以下几种SSR服务端渲染每次请求在服务端将组件渲染成完整 HTML 返回搜索引擎可以直接拿到内容。代价是服务端负载增加部署复杂度提升。NuxtVue 生态和 Next.jsReact 生态是主流的 SSR 框架。SSG静态站点生成在构建时生成完整 HTML 文件适合内容相对固定的站点。Gatsby、Astro、VitePress 都支持这种模式。SSG 比 SSR 更快因为不需要每次请求都跑一遍渲染。预渲染Prerender构建后对指定路由执行无头浏览器Puppeteer抓取生成静态 HTML 供爬虫访问。适合简单的营销页不适合数据变化频繁的页面。动态渲染Dynamic Rendering根据 User-Agent 判断是否爬虫爬虫请求返回渲染后的 HTML普通用户返回 SPA。这种方式需要服务端维护两套逻辑不推荐优先使用但兼容性很好。SEO 不只在框架层面还有一个容易被忽视的细节meta标签、title、description、og:系列标签Open Graph、robots.txt、sitemap.xml、语义化标签、图片alt属性、URL 结构规范化。这些基础工作做扎实比单纯换框架效果来得更直接。6. 高频手写题与实战排查技巧最后一部分我整理了一些面试必问的手写题和我在实际排查“诡异 Bug”时总结的经验。手写题考的是你“能不能不看文档写出一个可运行的实现”排查经验考的是你“遇到问题时的思路是否有章法”。6.1 手写题全家桶深拷贝、防抖节流、Promise.all手写题里最常考的是深拷贝。面试官通常会给一个包含对象、数组、日期、正则、函数的对象让你实现deepClone。我的实现思路分层递进function deepClone(value, map new WeakMap()) { if (value null || typeof value ! object) return value; if (value instanceof Date) return new Date(value); if (value instanceof RegExp) return new RegExp(value.source, value.flags); if (map.has(value)) return map.get(value); const result Array.isArray(value) ? [] : {}; map.set(value, result); Reflect.ownKeys(value).forEach((key) { result[key] deepClone(value[key], map); }); return result; }这里有两个关键细节第一用WeakMap记录已经拷贝过的对象解决循环引用问题a.self a这种第二Reflect.ownKeys可以拿到 Symbol 类型的 key而Object.keys拿不到。考到深拷贝时面试官一般还会追问“浅拷贝和深拷贝的区别”“JSON.stringify 做深拷贝有什么缺陷”答案分别是“浅拷贝只拷贝第一层属性”“JSON 方式无法处理函数、Symbol、undefined、循环引用还会把 Date 转成字符串”。Promise 的手写题也很高频常考Promise.all、Promise.race、Promise.allSettled。这里最容易出错的两个点一是空数组的处理——Promise.all([])应该立即 resolve 一个空数组Promise.allSettled([])同理Promise.race([])会一直 pending二是异常捕获——Promise.all中任何一个 promise reject整体立即 reject但不会中断其他 promise 的执行。写Promise.all的时候我习惯用Promise构造函数包一层内部通过一个计数器判断是否全部完成const myAll (promises) new Promise((resolve, reject) { const results []; let count 0; promises.forEach((promise, index) { Promise.resolve(promise).then((data) { results[index] data; count; if (count promises.length) resolve(results); }).catch(reject); }); if (promises.length 0) resolve(results); });其他手写题还有instanceof实现、new的实现、call/apply/bind的实现、防抖节流、Object.create、柯里化等这些都可以自己提前敲一遍不要只停留在“看懂了”的层面。手写题面试官看的是你能不能处理边界情况和细节代码风格是否干净。6.2 一段来自实战的答题建议面试前端岗位八股文是基础门槛但光靠背题还不够。我在做面试官的时候最欣赏的候选人是背得出概念也能讲清楚“自己在项目中踩过什么坑、怎么排查的”。比如你说你知道 HTTP 缓存不如补充一个实际场景“之前遇到一个需求需要页面刷新后立刻拿到最新灰度配置但接口被强缓存住了排查后发现是响应头里漏配了Cache-Control: no-cache后来在服务端统一加了缓存策略才解决。”这种回答既验证了你理解了缓存机制又展示了你的实战能力。我自己的经验是八股文需要“背”的部分只占三成剩下七成是要理解。理解之后你需要把知识点连成一张网。比如从“输入 URL 到页面展示”这条主线你可以延伸出 DNS 缓存、TCP 握手、HTTP 缓存、渲染流水线、回流重绘、跨域、性能优化、CDN、SSR/SEO 这一整套关联问题。面试官顺着这张网问下去你都能接得住这场面试基本就稳了。这种串联能力不是靠临时去背的而是项目里多思考“某个机制为什么这样设计”慢慢积累出来的。最后祝正在准备前端面试的朋友都能拿到心仪的 offer。