
前端面试题这玩意儿网上搜出来动辄上万篇文档但真正到面试现场被问到的翻来覆去就那么些基础点。2023年我把自己复习HTML、CSS、JS、打包、环境、网络这一路的笔记重新整理了一遍形成这份八股集合前后改了好几版后来身边不少朋友照着它刷题拿到了不错的offer。这份东西的定位很明确准备校招、想跳槽但不考算法岗的前端开发者或者刚学完前端三大件、想知道考试重点在哪的学习者。它不涉及框架源码级深挖而是把面试中重复率最高的基础题拿出来逐题拆解考的是什么、怎么答才不扣分顺带把容易踩的坑一并说清楚。所有内容都是2023年实际面试中高频出现过的方向不是我从哪本教材里抄来的标准答案。以下内容全部来自我的实际面试记录和复盘笔记按模块整理每道题后面附上我的理解方便你按自己的语言组织答案。1. HTML考点文档声明、语义化与iframe的实用边界1.1 第一行代码为什么总是 面试官问HTML十次有八次会先指一下页面第一行这行代码到底干了什么你要是答“声明这是HTML5”只能算及格得补上“渲染模式切换”这个核心点。的作用是告诉浏览器按照标准模式渲染页面而不是兼容模式Quirks Mode。兼容模式是当年为了兼容IE6时代不规范写法保留下来的一套“宽容”解析逻辑里面盒模型、行高、字体尺寸的算法都和现代标准不同。如果在2023年还让页面跑在兼容模式下大概率出现莫名其妙的间距、盒子尺寸对不齐、百分比宽度算错这类问题。我问过一个候选人他说“不写也能跑”。确实能跑但浏览器会自动触发兼容模式你写box-sizing: border-box在某些老版本浏览器里可能直接失效。所以规范里要求第一行写Doctype这不是形式主义是给渲染引擎定基调。!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title页面标题/title /head顺带说一句html langzh-cn这个属性也有面试官会闻到。它主要影响两件事屏幕阅读器用哪种语言规则朗读以及浏览器翻译插件按什么方向提示自动翻译。不写会有什么后果实测中一些无障碍审计工具会直接标warningSEO抓取时也可能影响页面内容的语种识别。这不是八股是能落地的细节。1.2 meta标签的职责边界meta charsetutf-8解决的是字符乱码问题。这个标签有两个前提一是必须在head里的靠前位置二是必须在任何文本内容前出现否则浏览器在解析到meta之前已经开始按默认编码读文本了。面试时可以补充一句HTTP响应头里的Content-Type会覆盖meta标签的声明所以生产环境乱码问题应该优先查服务端配置。meta nameviewport contentwidthdevice-width, initial-scale1几乎必考。移动端适配的起点就是这个标签它把页面可视区域的宽度绑定到设备宽度而不是让移动浏览器用一个980px的虚拟画布缩放渲染。面试中如果问“为什么移动端页面要加viewport”你就答不加的话文字会挤成一团用户需要双击缩放才能看清现代响应式设计全部建立在viewport之上。还有一个被问过的问题SEO相关的meta有哪些核心是description搜索引擎在列表页展示的摘要会优先取它。keywords在2023年基本已经不被主流搜索引擎采纳但作为基础八股还是需要知道它的存在。og:title这类Open Graph协议标签偶尔会出现在高级考点里作用是在微信、Facebook等社交平台分享链接时生成标题和摘要卡片。1.3 语义化标签的真正价值不在“好看”语义化标签这块面试官的隐藏意图是看你能不能在前端协作里减少无意义的div嵌套。header、nav、main、article、section、aside、footer这些标签的用法边界其实比较模糊很多候选人背书式的回答反而容易扣分。我面试时的答法是分三层一是对SEO友好搜索引擎能识别出页面主体结构和内容优先级虽然现在靠渲染后DOM也基本能判断但语义化仍然是最廉价的优化手段二是对无障碍访问友好屏幕阅读器会通过语义标签生成页面大纲方便视障用户跳转三是对开发维护友好五层深度的div嵌套代码新接手的人根本不知道哪个div是哪个区块语义化标签相当于代码注释。实操中我见过一个高频场景.icon图标和i标签的适配。页面里放图标用i classicon-xxx而不是span在小程序、部分H5框架里会出现伪元素失效问题因为i的font-style: italic默认样式会影响图标字体的字形。这类问题不算纯八股但面试官喜欢拿它来考基础功底。1.4 前端系统管理下的字典管理一般有啥用这个热搜词很真实做后台系统的人基本都会遇到。字典管理在系统中的定位是把状态码、类型枚举这种业务逻辑里的“数字或英文字符串”翻译成“用户能看懂的中文”同时集中维护一套可配置的映射表。举一个实际场景订单状态字段在数据库里存的是0、1、2、3分别表示待支付、已支付、发货中、已完成。如果前端直接把数字渲染到页面上用户看到的是天书在代码里写死switch判断每次加一个状态都得发版。字典管理的做法是把映射表放到后台配置里前端通过接口拉取后端加了一个状态码4前端不用改代码就能自动显示“已退款”。前端开发里字典的应用远不止下拉框和表格列。需要重点关注的是表格筛选条件里的下拉选项、详情页的状态标签色值、列表页的状态筛选联动以及导出文件的枚举字段转换。面试时能把这个场景完整讲出来比背十个CSS属性值管用得多。1.5 iframe的常见考点与父页面通信iframe在2023年已经不是主流布局方案但面试题里它仍然占有一席之地而且问法非常集中为什么不推荐用iframe如何实现父页面和iframe之间的通信如何关闭iframe并刷新父页面不推荐的理由主要有四个一是iframe会阻塞父页面的onload事件拖慢首屏性能二是每个iframe都是一个独立文档内存和网络连接开销都成倍增加尤其是移动端一个页面塞好几个iframe很容易直接卡崩三是对SEO不友好搜索引擎对iframe内的内容索引效果很差四是安全问题跨域iframe很容易成为点击劫持攻击的载体需要X-Frame-Options或CSP配合防护。父页面与iframe通信是必考八股。同域情况下可以直接通过contentWindow访问iframe的DOM// 父页面获取iframe内的元素 const iframe document.getElementById(myFrame); const innerDiv iframe.contentWindow.document.getElementById(innerDiv);跨域时contentWindow.document会被浏览器的同源策略拦截只能通过postMessage通信。这几乎是标准答案但很多人犯了基础概念不清的毛病——把postMessage当成jQuery的方法实际上它是浏览器原生API// 父页面发送消息 iframe.contentWindow.postMessage({ type: CLOSE, action: refresh }, *); // 子页面接收消息并关闭自身、通知父页面刷新 window.addEventListener(message, (event) { if (event.data.type CLOSE) { window.parent.postMessage({ type: PARENT_REFRESH }, *); } });“iframe关闭并刷新父页面”这个操作里需要注意的关键点是子页面不能直接点击按钮改动父页面的location.href因为跨域策略不允许。正确姿势是子页面通过postMessage通知父页面由父页面自己调用location.reload()。如果两者同域直接window.parent.location.reload()也能执行。我面试时会特意追问“跨域怎么办”能答出postMessage方案的人基本就过关了。2. CSS布局与视觉实现flex、动画、兼容性真题2.1 flex布局的核心考法与常规答法CSS flex的内容占据了前端面试题的基础半壁江山近五年几乎每场一面都会问。常规问法是“谈谈flex布局”你把display: flex、flex-direction、justify-content、align-items这几个属性背一遍就够了。但二面三面往往会追问一个更痛的点子元素的flex属性是如何分配空间的。flex是flex-grow、flex-shrink、flex-basis的缩写。默认值是0 1 auto。这三个参数的含义是flex-grow表示剩余空间的瓜分比例flex-shrink表示空间不足时的收缩比例flex-basis定义项目在分配主轴空间之前的默认尺寸。最简单的理解方式是把主轴想象成一个蛋糕flex-grow决定了多出来的蛋糕怎么分flex-shrink决定了蛋糕不够时谁先饿肚子。经典题目来了三个item第一个flex: 1第二个flex: 2第三个flex: 3父容器宽度在子元素默认宽度总和的2倍时三个item如何分布答案是先按各自内容的flex-basis默认为auto取内容的width占掉一部分宽度剩余空间按1:2:3的比例分给三个item。为什么是先减去basis再分配这是关键——flex分配的是剩余空间不是总空间。我自己的经验是面试官通常会追问一道更实际的场景题“父容器宽度500px三个子元素基础宽度分别为100px、200px、100pxflex-shrink设置为0、1、2父容器变成200px时哪个元素会溢出”这道题考察的是flex-shrink的计算逻辑收缩比例不是简单的1:2直接分而是按照“基础宽度 × shrink系数”加权计算。100×0完全不被压缩200×1和100×2合计400200宽的压缩量为100×(200/400)50px最终变为150px100宽的压缩量为100×(100/400)25px最终变为75px。能亲手算对这道题的人不多但算过的都一致认为flex的实际规则和“想当然”差距极大。2.2 垂直居中的所有方案与推荐顺序垂直居中是个老生常谈的问题面试官问它不是为了听你背方案而是看你在真实项目中是否能根据布局场景选择最合适的手段。通常给到的方案有使用flex父元素display: flex; align-items: center; justify-content: center;这是最推荐的方案兼容性和语义都很好。使用grid父元素display: grid; place-items: center;代码最精简但需要项目支持grid。使用margin: auto对固定宽高元素有效不需要flex但要保证父元素高度确定。使用绝对定位transformposition: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);多用于弹窗定位缺点是不支持IE9以下。使用line-height居中只适用单行文本用于按钮、标签类元素非常快。我回答这个问题的惯用思路是先问清楚元素的宽高是否已知、父容器是否定义高度、是否可能要适配移动端。如果三个条件都不明确我首先选flex如果元素是弹窗类我会选absolutetranslate因为flex方案在弹窗定位上还需要额外控制z-index层级和滚动穿透问题。2.3 CSS动画的性能优化与GPU加速CSS动画是面试题里区分“会写样式”和“理解渲染原理”的分界线。常见问法“用css3实现一个无限循环的旋转动画”或者“为什么transform和opacity做动画性能好而top和left做动画性能差”答案的核心在渲染进程的合成层compositor机制。transform和opacity动画可以在合成器线程直接完成不触发重排reflow和重绘repaint而修改top、left、width、height这类几何属性必须经过Layout和Paint阶段主线程计算量级完全不同。可以这么说一个跑在合成器上的动画和跑在主线程上持续触发布局计算的动画性能差距可能达到10倍以上。实现CSS3旋转动画的代码非常简单.spinner { width: 40px; height: 40px; border: 4px solid rgba(0, 0, 0, 0.1); border-top-color: #3498db; border-radius: 50%; animation: spin 1s linear infinite; } keyframes spin { to { transform: rotate(360deg); } }关于rotate3d这个进阶考点表面问法是“实现三维翻转效果”实际考察的是transform-style: preserve-3d和perspective的组合使用。我对rotate3d(1, 1, 0, 45deg)这句代码的解释是前三个数字定义旋转轴向量x、y、z三个方向的分量第四个数字是旋转角度。它可以实现类似Card翻转、立方体展示等效果。需要注意的是只有加上perspectivetransform的Z轴位移才能真正产生近大远小的透视效果否则只是视觉上的平面缩放。这个点很多面试官会用“为什么我的3D立方体看起来是平的”这种实际问题来考。2.4 字体渐变与涟漪光圈扩散高频实现题“字体渐变”这道题的重点是background-clip: text属性。常规文字颜色用color控制想要实现从红到蓝的渐变文字需要把背景色限定在文字轮廓范围内.gradient-text { background: linear-gradient(90deg, #ff6b6b, #4ecdc4); -webkit-background-clip: text; background-clip: text; color: transparent; }很多候选人答出这行代码之后就不再深入了。面试官的追问往往是你为什么要设置color: transparent因为背景是铺在元素盒模型上的背景被裁剪到文字形状之后文字自身的颜色仍然会盖在背景上方如果不把文字颜色设为透明渐变背景根本看不见。这个细节很能体现是否真正理解background-clip的机制。“涟漪光圈扩散”在热搜词里也有实际场景是按钮点击动画、地图标注扩散效果。实现方式一般分为两层一层是静态的元素另一层是不断放大并淡出的伪元素或子元素。核心动画代码如下.ripple { position: relative; width: 60px; height: 60px; border-radius: 50%; background: rgba(0, 150, 255, 0.6); } .ripple::after { content: ; position: absolute; inset: 0; border-radius: 50%; background: rgba(0, 150, 255, 0.3); animation: ripple 1.5s ease-out infinite; } keyframes ripple { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(2.5); opacity: 0; } }要把这个动画做好实践经验里需要注意两点第一::after必须是绝对定位否则会撑大父元素导致布局跳动第二动画时间、缩放倍率和ease-out曲线三者配合模拟水波扩散的自然衰变感linear曲线做出来的效果明显生硬。这个方案在大厂面试题中出现频率很高属于“看上去复杂、讲透原理其实很简单”的题目。2.5 Vue的:deep()样式穿透与原理:deep()本身不是一个CSS属性而是Vue单文件组件中scoped样式提供的深度作用选择器。面试里出现这个关键词通常的题目是“scoped样式如何实现隔离子组件内部元素如何被父组件样式命中”先说scoped的原理Vue在编译单文件组件时会为当前组件的DOM节点添加一个唯一的>/* 父组件中修改el-input内部input的样式 */ :deep(.el-input__inner) { border-color: #409eff; }面试中这个考点通常和CSS优先级、选择器权重一起出现因为:deep()本身不会改变选择器权重如果你写的类名选择器和子组件内部的类名权重相同最终生效的还是子组件内部定义的那一条。我建议在讲:deep()时主动提一下这个权重陷阱因为实际开发中很多人发现“写了但不生效”多半是权重和顺序的问题。2.6 小程序苹果底部兼容问题“小程序苹果底部兼容css”这个热搜词对应的是iPhone全面屏底部的home indicator横条安全区域。小程序页面固定底部的按钮、导航条如果没有做安全区适配会出现被横条遮挡的情况。解决方案一般是使用CSS的env(safe-area-inset-bottom)和constant()配合.safe-bottom { padding-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 - 11.2 */ padding-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */ }这里有一个容易踩的坑constant()是老版本iOS的安全区变量写法必须先写env()是后续版本的标准写法必须后写。如果顺序反了老版本浏览器读到不能识别的env()会把整条声明丢弃反过来先写constant()再写env()老版本用前者新版本用后者从视觉上看是一致的。小程序项目里还需要注意page根元素是不是position: fixed布局。如果页面最外层已经固定定位安全区底部的padding需要加在固定定位元素上而不是加在page上。这个问题在面试现场还原场景时经常被翻出来考验——候选人知道env()却不知道应该加在谁身上我能理解这种“背过但没踩过坑”的答案项目实际跑一次就记住了。3. JavaScript高频八股类型、异步与原型链3.1 typeof与instanceof的类型判断陷阱类型判断在JavaScript里是面试题的高频起点。如果面试官让你“判断一个变量的类型”最佳答案不是单一的typeof或instanceof而是根据场景混合使用。typeof适合判断原始类型但它有两个经典坑typeof null objecttypeof function(){} function但不属于常规基础类型。instanceof适合判断引用类型但它依赖于原型链[] instanceof Object为true[] instanceof Array也为true。要精确判断数组通常用Array.isArray()。要判断普通对象、数组、Set、Map、Date这类复杂类型最稳妥的方案是Object.prototype.toString.call()Object.prototype.toString.call([]); // [object Array] Object.prototype.toString.call(new Date()); // [object Date] Object.prototype.toString.call(() {}); // [object Function]这个方法的原理是利用Object.prototype上的toString方法在调用时会读取this的内部[[Class]]属性。当你想判断一个类型时不要直接调用[].toString()数组的toString方法被重载过返回的是元素拼接字符串必须借用Object.prototype上的原始方法。这个细节就是面试官想听的“理解机制”而非“背API”。3.2 JS判断字符串是否包含indexOf、includes、search、match这个热搜词对应的是一道极基础的面试题但考察的点在于“这么简单的事情你选哪个API”。四种常见方式str.indexOf(sub)返回子串首次出现的下标找不到返回-1。str.includes(sub)返回布尔值语义最直接。str.search(regexp)接受正则返回匹配下标用于复杂模式匹配。str.match(regexp)返回匹配结果的数组适合需要提取匹配内容的场景。如果是单纯判断是否包含我会首选includes因为它返回布尔值语义清晰代码不会出现“indexOf -1”这种不直观的写法。需要进一步拿到位置信息时再用indexOf。需要匹配到具体内容比如邮箱格式时用match配合正则。这道题如果出现在一面通常还会追问“大小写敏感吗”、“如果只匹配开头或结尾呢”。大小写默认敏感要忽略大小写可以用toLowerCase()先统一或者正则加i标志。匹配开头结尾对应startsWith、endsWith这两个方法在URL路径判断上非常常用。组合使用还会衍生一个高频题如何判断URL中的查询参数是否存在一般用URLSearchParams更完整。3.3 事件循环与经典执行顺序分析JavaScript事件循环是面试硬骨头几乎每场必考。核心概念包括调用栈Call Stack、任务队列Task Queue宏任务、微任务队列Microtask Queue、事件循环的调度逻辑。最经典的一道题是console.log(1); setTimeout(() { console.log(2); }, 0); Promise.resolve().then(() { console.log(3); }); console.log(4);输出顺序是1、4、3、2。解析逻辑同步代码先执行完1、4微任务队列里的Promise回调在宏任务之前执行所以输出3最后才是宏任务队列中的setTimeout回调输出2。这个题的关键是理解微任务比宏任务拥有更高的优先级且每执行完一个宏任务都会清空整个微任务队列。面试中如果题目进一步复杂化比如加了async/await要记住一个转换await后面的代码相当于被包进.then()属于微任务。还有一个常见陷阱是Promise构造函数内的代码是同步执行的只有.then、.catch的回调才是异步。这道题我反复强调因为它是整个异步编程理解程度的“试金石”背答案没用自己用node或浏览器console跑一遍才能形成肌肉记忆。3.4 闭包、防抖与节流的综合问法闭包面试八股的经典定义是函数作用域内的变量在函数执行结束后仍然能被外部引用。用一个简单例子function outer() { let count 0; return function inner() { count; console.log(count); }; } const counter outer(); counter(); // 1 counter(); // 2为什么count没有被垃圾回收因为inner函数的作用域链仍然持有outer的活动对象引用垃圾回收器判断count仍然可达就不会回收它。这是闭包的本质也是内存泄漏的根源。面试时如果能主动补一句“闭包如果使用不当可能造成内存泄漏所以要注意及时解除引用”会显得思考更完整。防抖和节流是闭包最典型的生产级应用场景。区分两者的一句话防抖是“疯狂点击只执行最后一次”节流是“疯狂点击按固定频率执行”。举个例子搜索框输入实时请求接口适合用防抖浏览器resize事件里同步计算布局适合用节流。function debounce(fn, delay) { let timer null; return function (...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; } function throttle(fn, interval) { let last 0; return function (...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }手写这两个函数几乎是面试必考。需要注意的细节包括this绑定要透传参数要透传防抖的定时器要能重置。如果面试官要求“第一次点击立即执行”还需要加immediate参数的版本。能在白板上顺手写出带immediate的防抖足以证明你真的理解而非背代码。3.5 this的四个方向与箭头函数的例外this指向问题核心规则可以压缩成四个场景函数直接调用非严格模式this指向window浏览器环境或globalThis。对象方法调用this指向调用该方法的对象。构造函数new调用this指向新创建的实例。call、apply、bind显式绑定this指向传入的第一个参数。这里最容易答错的是组合情况比如“把一个对象的method赋值给一个变量再调用this指向谁”答案是看函数调用方式不看函数定义位置。赋值给变量之后调用变成了普通函数调用this回到window或undefined严格模式。这也是闭包和this经常一起出现的原因。箭头函数是特例它没有自己的this箭头函数内的this在定义时由外层作用域决定调用方式不影响。这个特性让它天然适合做事件回调、定时器回调、异步回调里的this透传。面试官问到“为什么Vue的methods里写箭头函数不能拿到组件实例”就是因为箭头函数定义时捕获的是组件定义作用域中的this组件实例还没创建拿不到。3.6 深拷贝与浅拷贝的完整实现浅拷贝只复制一层属性深拷贝递归复制所有层级。常用浅拷贝方式有Object.assign、展开运算符...深拷贝的标准答案分两个级别初级答案const cloned JSON.parse(JSON.stringify(original));这个方案有个明显的坑对象里的undefined、function、symbol、RegExp、Date等特殊类型会丢失或被转成字符串。面试官都会追问“这个方案有什么问题”你必须能说出至少两个缺陷一是函数和undefined属性被丢弃二是循环引用会直接抛错。进阶答案是手写一个递归深拷贝并处理特殊情况function deepClone(target, map new WeakMap()) { if (target null || typeof target ! object) { return target; } if (map.has(target)) { return map.get(target); } const isArray Array.isArray(target); const cloneTarget isArray ? [] : {}; map.set(target, cloneTarget); if (isArray) { target.forEach((item, index) { cloneTarget[index] deepClone(item, map); }); } else { for (const key in target) { if (Object.prototype.hasOwnProperty.call(target, key)) { cloneTarget[key] deepClone(target[key], map); } } } return cloneTarget; }使用WeakMap处理循环引用是重点WeakMap的键是弱引用不会阻止原有对象被垃圾回收避免深拷贝过程中的内存泄漏。这几个词说出来面试官基本就知道你对数据结构是有理解深度的而不只是背了一段递归代码。3.7 Promiseall、race、allSettled与异常处理Promise是异步编程的高频考区近两年的问法从“手写Promise.all”逐渐转向“讲讲allSettled和all的区别”。核心区别一句话Promise.all中任何一个promise被rejected整体立即进入rejected状态Promise.allSettled会等待所有promise都settled无论成功或失败再返回每个promise的结果数组。Promise.allSettled([p1, p2, p3]).then((results) { results.forEach((result) { if (result.status fulfilled) { // 使用 result.value } else { // 处理 result.reason } }); });生产环境里一个页面有三个独立的接口请求需要同时发出又不想因为其中一个失败导致整个页面白屏首选allSettled而不是all。这个语义上的差别就是面试官想听的“你会不会在真实项目里选错API”。Promise还有一个必考点是catch后面的代码是否还会执行。我的经验是Promise链式调用中任何位置的rejected都会被下游最近的catch捕获catch执行完后如果没有继续抛错后续的then仍然正常执行。很多候选人会误认为一旦catch就整个链终止了这是致命的认知错误。4. 打包与环境Webpack、Babel、环境配置面试问答4.1 Webpack核心概念与其存在的原因为什么需要打包工具2023年的前端项目不可能再像十年前那样直接在HTML里用script引一堆JS文件。ES Module虽然已经被现代浏览器原生支持但生产环境仍然面临几个问题模块请求过多导致性能差一个页面几十个script请求、第三方库依赖管理复杂、开发环境需要热更新和类型检查。打包工具把这些事情统一解决掉就成了工程化的事实标准。Webpack面试题首要考核的是五个核心概念entry打包入口Webpack从这里构建依赖图。output打包产物输出位置和文件名规则。loader将非JS文件转换成Webpack能处理的模块是“翻译器”。plugin在打包生命周期各个阶段注入额外能力是“增强器”。modedevelopment、production、none决定构建时启用哪些内置优化。面试官特别喜欢问loader和plugin的区别。我惯用的类比是loader是做格式转换的专用工人比如把TypeScript翻译成JavaScript把Sass编译成CSSplugin是负责管理整个工地的项目经理它可以监听打包的每个阶段、修改产出文件、做代码压缩、生成HTML模板、把公共代码抽离出来。一个管“怎么把文件变成模块”一个管“整个打包流程怎么编排”二者互不替代。4.2 loader执行顺序为什么是“从右到左”这是Webpack八股中一道“看着简单答不透彻就露馅”的题。比如配置{ test: /\.scss$/, use: [style-loader, css-loader, sass-loader] }实际的执行顺序是sass-loader-css-loader-style-loader即从右往左。这个设计的底层原因是每个loader只是接收前一个loader的输出并输出下一个loader能处理的形式。sass-loader把SCSS编译为标准CSScss-loader处理CSS里的url()、import并把CSS变成JS模块style-loader再把这段CSS字符串通过style标签注入页面。从左到右看的话第一步就要把还不是标准CSS的内容塞进style标签逻辑上就不通。面试中如果想加分可以补充enforce: pre和enforce: post这个点。pre会在普通loader之前执行post在之后执行常用于eslint-loader这种“代码检查先行”的场景。4.3 Babel的原理和Webpack的关系Babel出现的原因很简单浏览器的JS版本支持进度参差不齐ES6的新语法需要转换成ES5才能在老浏览器跑。它的工作流程分三步解析parse、转换transform、生成generate。解析把源码字符串转换成AST抽象语法树这一步相当于把一句话拆成语法成分。转换遍历AST对节点做增删改实现“翻译”比如把箭头函数改成普通函数把const改成var。生成把修改后的AST重新转成代码字符串并生成source map。Webpack里的babel-loader就是把这个流程封装成一个loader。它把匹配到的JS模块交给Babel处理Babel根据.babelrc或babel.config.js里的presets和plugins配置决定做什么转换。常见presets有babel/preset-env按目标浏览器转换语法、babel/preset-react转换JSX、babel/preset-typescript转换TS。需要注意的是Babel默认只转换语法不会补齐API比如Array.prototype.includes在IE11上不存在这需要另外引入core-js做polyfill。面试中能把“语法转换”和“API兼容”分开讲说明你踩过线上老浏览器的坑。4.4 Tree Shaking、副作用与Code SplittingTree Shaking是Webpack在production模式下默认开启的功能核心意思是“只打包用到的代码删除没用到的导出”。它的前提条件有三个模块必须是ES Module语法import/export不能用CommonJS的require不能在被webpack标记为有副作用sideEffects: false的包中使用构建时处于production模式。一个常见的实际问题是“为什么import { debounce } from lodash打包体积还是很大”因为lodash整体是一个CommonJS模块不是ESMTree Shaking对它的作用有限。解决方案是import debounce from lodash/debounce或者使用lodash-es。这个坑我面试时至少听候选人提过两次都是真实项目里踩过的。Code Splitting代码分割是打包面试的另一个进阶考点。按路由拆包、按第三方库拆包首屏只加载当前页面用到的JS其他模块在跳转时懒加载。核心配置是把output.filename里的hash策略设置为chunkhash或contenthash否则每次改代码所有文件名都变化浏览器缓存就失效了。生产环境缓存优化是打包面试里的最后一环contenthash根据文件内容生成哈希内容不变文件名就不变用户下次访问直接从浏览器缓存里取大幅减少带宽消耗和加载时间。4.5 环境配置NODE_ENV、代理与多环境打包前端环境配置看起来简单实际坑非常多。开发和线上环境最核心的是三个差异接口地址不一样、调试工具开关不一样、构建优化参数不一样。NODE_ENV是Node.js里的标准环境变量development、production、test三个值决定了Webpack、Babel、React等的许多默认行为。在npm脚本里通过cross-env设置跨平台的环境变量scripts: { dev: cross-env NODE_ENVdevelopment webpack serve, build: cross-env NODE_ENVproduction webpack, build:test: cross-env NODE_ENVproduction BUILD_TARGETtest webpack }多环境接口代理也是必考题。开发环境通常用webpack-dev-server的proxy配置解决跨域前端请求/api开发服务器把它转发到后端实际地址浏览器看到的请求是同源的就绕过了CORS问题。线上环境的跨域则只能靠后端配置Access-Control-Allow-Origin或者Nginx反向代理。我在面试中见过最多的问题是“为什么开发时接口能通部署到服务器上就404”这通常是history路由模式没有在服务器端配置try_files回退到index.html或者前端请求的baseURL在环境切换时还写死成http://localhost:3000。环境配置的核心思路是所有接口地址必须通过环境变量注入而不是写死在业务代码里否则换环境就得改代码重新部署。4.6 前端系统开发中的skills与常见岗位要求“前端开发skills”这个热搜词侧面反映了当下招聘市场的真实需求。2023年前端岗位的基础技能栈已经不再只是HTMLCSSJS了。工程化能力、版本管理、调试能力、性能优化意识和团队协作能力都成了默认要求。整理一份最低限度的技能清单大致是HTML语义化、SEO基础、Canvas基础、SVG基础、可访问性CSS布局flex/grid、响应式、动画、预处理器Sass/Less、CSS ModulesJavaScriptES6、DOM/BOM、异步编程、模块化ESM/CommonJS、设计模式基础框架至少熟练掌握Vue或React中的一种能讲清楚生命周期和组件通信工具链Webpack或Vite、Git、ESLint/Prettier、npm/yarn/pnpm浏览器渲染原理、事件循环、缓存机制、跨域方案、DevTools调试网络HTTP、HTTPS、TCP基础、DNS面试时被问到“你擅长什么”时不需要把这张表全部背出来而是挑一两个你真正花时间研究过的方向展开。比如你对性能优化有实际经验就详细讲一个你做的首屏加载优化案例从分析指标到改动细节再到前后数据对比。这比“我都了解一点”有说服力得多。5. 网络基础从URL输入到HTTP缓存与跨域5.1 输入URL到页面渲染的完整过程网络部分是前端面试的“综合素质题”考察你是否把一个请求从用户输入到页面展示的完整链路理解清楚。标准答案一般包含以下步骤浏览器解析URL判断协议、域名、端口、路径。查找DNS缓存浏览器缓存、系统缓存、路由器缓存、DNS服务器得到目标服务器IP。浏览器与服务器建立TCP连接三次握手。如果协议是HTTPS额外进行TLS握手协商加密密钥。浏览器发起HTTP请求携带请求头Cookie、User-Agent、Accept等。服务器处理请求返回HTTP响应状态码、响应头、响应体。浏览器根据响应头判断缓存策略决定是否直接使用本地缓存。浏览器解析HTML构建DOM树解析CSS构建CSSOM树两者合成渲染树。布局计算Layout计算每个节点的几何位置。绘制Paint把节点绘制到页面画布。合成Composite把分层画面组合输出到屏幕。面试官通常会让候选人重点讲第8到第11步其中“为什么CSS放在head、JS放在body底部”这个经典问题就藏在这个链路里CSS会阻塞渲染render-blockingJS在解析过程中会阻塞DOM构建所以JS要放到底部或使用defer/async属性。5.2 TCP三次握手与四次挥手TCP三次握手几乎是网络面试的“送分题”但也是“扣分重灾区”。标准回答是第一次握手客户端发送SYN报文进入SYN_SENT状态表示“我要建立连接”。 第二次握手服务器收到后返回SYNACK报文进入SYN_RCVD状态表示“我收到了我也准备好建立连接”。 第三次握手客户端收到后发送ACK报文进入ESTABLISHED状态表示“我确认了连接建立成功”。为什么不是两次一个常见例子是如果客户端发出的SYN报文因为网络延迟超时重传第一次连接已经结束后延迟到达的SYN报文会让服务器误以为是一个新连接导致服务器维持一个多余的半连接资源。第三次握手的ACK就是让对方确认“我是认真的这个连接确实存在”避免历史重复连接在服务器端产生残留。四次挥手顺序是客户端发送FIN我发完了- 服务器返回ACK我收到了你的结束请求- 服务器发送FIN我也发完了- 客户端返回ACK我确认关闭。这里有两个细节容易被问住第一中间两步不能合为一步因为服务器收到FIN后可能还有数据需要继续发送所以要先回应ACK等数据发完再发FIN第二客户端在发送最后一个ACK之后要进入TIME_WAIT状态并等待2MSL确保最后的ACK能到达服务器否则服务器无法确认关闭会重发FIN。能讲到TIME_WAIT的候选人面试官一般会觉得网络基础比较扎实。5.3 HTTP状态码与版本演进状态码是网络八股里最基础的知识板但有区分度的考点集中在“容易混淆”的几个数字上301永久重定向与302临时重定向搜索引擎对301会转移权重302不会。304Not Modified配合协商缓存使用表示资源未修改可直接用本地缓存。400Bad Request与401Unauthorized与403Forbidden400是请求报文本身有问题401是未认证403是无权限访问。能准确区分这三个面试官会认为你的项目里处理过真实接口错误。502Bad Gateway与503Service Unavailable与504Gateway Timeout502是网关从上游收到了无效响应503是服务暂时不可用通常过载维护504是网关等待上游响应超时。HTTP协议版本演进也是必考HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3。HTTP/1.1新增了持久连接Connection: keep-alive、Host字段、块级传输编码、缓存相关的Cache-Control等。HTTP/2引入了多路复用一个TCP连接并行多个请求、二进制分帧、头部压缩HPACK、服务器推送。HTTP/3基于UDP的QUIC协议解决了HTTP/2的队头阻塞问题。面试官问“HTTP/2的多路复用解决了什么问题”本质是问HTTP/1.1的队头阻塞。HTTP/1.1在一个持久连接上同一时间只能处理一个请求浏览器最多对同一域名开6个左右的并行连接大量请求时必须在队列里排队。HTTP/2通过在一个TCP连接里用二进制帧交错传输多个流彻底解决了请求层面的队头阻塞。但“TCP层的队头阻塞”在HTTP/2下仍然存在这也是HTTP/3转向UDP的根本原因。5.4 强缓存与协商缓存的完整链路浏览器缓存是网络面试的核心题常有面试官用一道“页面更新了用户却看到旧版本”的实际问题来考。完整答案分两条线强缓存和协商缓存。强缓存浏览器发起请求前先检查缓存如果缓存未过期直接使用本地缓存不发请求到服务器。相关响应头是Cache-Controlmax-age7200表示2小时和ExpiresHTTP/1.0的绝对过期时间优先级低于Cache-Control。协商缓存强缓存失效后浏览器带着缓存标识If-None-Match带上响应头中的ETag值或If-Modified-Since带上Last-Modified向服务器发起请求由服务器判断资源是否修改过。如果没修改返回304浏览器使用本地缓存如果修改过返回200带上新资源。解题的关键是优先级Cache-ControlExpiresETagLast-Modified。其中ETag比Last-Modified更精确因为Last-Modified精度只能到秒文件在1秒内多次修改时可能判断不出变化。实际部署中的常见问题前端项目改了代码用户浏览器还在用旧的JS文件。解决方法是打包引入contenthash文件名并在静态资源的Cache-Control里设置max-age31536000且immutable因为文件名变了资源就是全新URL旧缓存不会生效而index.html本身设置为no-cache永远走协商缓存保证每次访问都能拿到最新版本号引用的资源入口。5.5 同源策略与CORS跨域方案网络面试跨域题的基本框架是先讲清楚什么是同源协议、域名、端口三者一致再讲为什么要有同源策略保护Cookie、LocalStorage等用户数据不被恶意站点读取最后给出具体场景的跨域方案。JSONP利用script标签不受同源策略限制的原理只支持GET已经逐渐被淘汰但面试题中仍然会出现因为它的原理能考察你对“浏览器加载外部资源”的理解。CORS服务器在响应头里加Access-Control-Allow-Origin浏览器根据这个头决定是否允许前端读取响应。面试时要把“简单请求”和“预检请求preflight”的机制讲清楚非简单请求自定义请求头、非GET/POST/PUT/DELETE、Content-Type不是application/x-www-form-urlencoded等会先发一个OPTIONS请求确认允许再发正式请求。通过代理服务器转发开发环境下用webpack-dev-server的proxy生产环境用Nginx反向代理。代理方式能绕开跨域的根本原因是浏览器只对XMLHttpRequest/fetch触发的跨域限制对服务器与服务器之间的通信没有同源限制。值得注意的一个细节是跨域请求即使响应失败服务器可能已经处理了请求。CORS的限制是浏览器阻止了前端读取响应不是服务器拒绝处理。面试中答错这个点的人不少很多人以为跨域请求根本不会到达服务器实际上请求照样发过去了只是响应被浏览器拦了。这个误解在处理“跨域接口造成重复下单”这类生产问题时会很致命。5.6 网络通信协议里WebSocket与CDN的务实应用网络通信协议这个热搜词范围很大前端面试里最常问的是WebSocket和CDN这两个与实际业务强相关的点。WebSocket实现了客户端与服务器的全双工通信只需要一次HTTP握手之后双方随时可以互相推数据。常见应用场景有聊天室、实时通知、在线协作、股票行情、多人游戏。面试中如果问“前端如何实现一个实时消息通知”不能只说轮询特别要指出轮询的问题是高频率请求浪费带宽和服务资源消息延迟取决于轮询间隔。WebSocket的优势是服务端可以主动推送实时性最高。CDN的原理是把静态资源缓存到离用户地理位置更近的边缘节点用户请求时从最近的节点返回资源缩短网络传输距离。前端面试的追问通常是“CDN缓存和浏览器缓存的区别”CDN缓存发生在网络层浏览器缓存发生在本地CDN缓存可以让不同用户共享同一份缓存副本浏览器缓存只能自己用。部署上线时改了代码记得刷新CDN缓存否则用户拿到的还是旧资源——这个问题我至少听过三个候选人说了同一种踩坑经历。结尾的建议面试前怎么刷八股这是我踩过几次坑之后的体会我从2022年下半年到2023年前前后后经历了大概十几场前端面试从大厂到中型公司都聊过。最大的体会是八股不能死记硬背每道题都要在自己的项目里找到对应场景。面试官问“什么是闭包”你可以背定义也可以结合自己数据大屏项目里的防抖封装来讲后者显然更能让面试官记住你。我推荐的刷题方式是三遍法第一遍按模块通读把陌生概念搞懂第二遍关上文档想这道题的答案不管多磕巴都要用自己的话讲完整第三遍针对卡壳的地方回到原文复盘再手写一遍代码实现。不求一次做完一个周末过一遍HTMLCSS一个周末过一遍JS打包和网络各半天比临时抱佛脚打疲劳战有效得多。最后分享一个小技巧面试结束前反问面试官“这个岗位的代码规范和后端协作方式是什么样的”既能体现你对工程化的理解也能帮你过滤掉面试体验差的团队。祝大家都能拿到心仪的offer。