前端面试精选 28 题
HTML/CSS · JavaScript · React · 工程化 · 浏览器 · 性能 · 微前端 · 手写代码 · 软技能,覆盖前端面试最高频考点。
前端面试精选 28 题
精选前端面试最高频考点:每题「一句话本质 + 完整答案 + 追问弹药」。覆盖 HTML/CSS · JavaScript · React · 工程化 · 浏览器 · 性能 · 微前端 · 手写代码 · 软技能 九大类。完整 130 题见源文档
前端题库/前端面试真题130题完整答案.md,通用考点详答见前端题库/index.md。
一、HTML & CSS(5 题)
1. 语义化的意义
- 答案:用恰当含义的 HTML 标签(
header/nav/article/section/aside/footer)构建页面,而非通篇div/span - 为什么重要:
- 对开发者友好:代码结构清晰、易维护、团队协作顺畅
- 对 SEO 友好:搜索引擎爬虫依赖标签判断内容与权重
- 对无障碍友好:屏幕阅读器根据语义标签更好地朗读
2. 标准盒模型 vs IE 盒模型
| 模型 | width 含义 | 元素实际占宽 |
|---|---|---|
标准盒模型(content-box) | content 的宽度 | width + padding + border + margin |
IE 盒模型(border-box) | content + padding + border | width + margin |
border-box在实际开发中更常用——让设置的宽度直接就是元素可见宽度总和,方便布局(尤其百分比宽度和内边距同时存在时)。
3. BFC(块级格式化上下文)
- 本质:Web 页面的一块独立渲染区域,内部元素布局不影响外部元素
- 触发条件(任一):根元素、
float ≠ none、position: absolute/fixed、display: inline-block/table-cell/flex/grid、overflow ≠ visible - 解决的三大问题:
- 清除浮动(父元素高度塌陷)
- 防止外边距重叠(不同 BFC 的 margin 不重叠)
- 阻止元素被浮动元素覆盖(自适应两栏布局)
4. 重绘 vs 回流
- 回流(Reflow):布局/几何信息改变(窗口大小、增删 DOM、改宽高位置)→ 重新计算元素位置和大小
- 重绘(Repaint):外观改变(颜色、背景色、
visibility)但不影响布局 → 重新绘制 - 如何避免:
- 一次性改
class或cssText,避免逐条修改样式 - 多次重排的元素先
display: none,操作完再显示 - 用
documentFragment批量操作 DOM - 复杂动画用
position: absolute/fixed脱离文档流 - 避免
table布局(小改动可能引起整个表格回流)
- 一次性改
5. 移动端适配:rem / vw / 1px 问题
- rem 方案:JS 动态算视口宽设
html font-size,元素用rem等比缩放(手淘 flexible.js) - vw/vh 方案:
vw = 视口宽的 1%,直接width: 20vw替代 JS - 1px 问题:高 DPR 屏幕 CSS 1px 被渲染成 2~3 物理像素偏粗
- ❌ 不能直接
transform: scale(0.5)整个元素(内容和文字一起缩) - ✅ 给伪元素画边框再缩放它
- ❌ 不能直接
.hairline::after {
content: ''; position: absolute; left: 0; top: 0;
width: 200%; height: 200%;
border: 1px solid #ddd;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
}
@media (-webkit-min-device-pixel-ratio: 3) { /* DPR=3 时 scale(0.333) */ }
二、JavaScript 核心(7 题)
6. 闭包
- 本质:有权访问另一个函数作用域中变量的函数(典型形式:函数内部嵌套函数并 return)
- 原理:利用 JS 词法作用域,内部函数引用外部函数的变量,即使外部函数执行完,变量对象也不销毁(因为被内部函数引用)
- 应用场景:
- 创建私有变量(模拟私有方法)
- 函数柯里化(多参数→单参数链)
- 循环中保存变量状态(
var的经典坑,用 IIFE 或let解)
- 缺点:可能内存泄漏 → 退出函数前把不再用的局部变量置为
null
7. 原型与原型链
- 每个函数有
prototype,指向原型对象;每个对象有__proto__,指向创建它的构造函数的prototype - 访问对象属性时找不到会沿
__proto__向上查,直到Object.prototype.__proto__ === null——这就是原型链 - 三种继承方式对比:
- ES5 组合继承:
Parent.call(this)+Child.prototype = new Parent(),调用两次父构造函数(缺点) - ES5 寄生组合继承:
Parent.call(this)+Child.prototype = Object.create(Parent.prototype)+Child.prototype.constructor = Child——最理想的 ES5 继承 - ES6
class extends:本质是寄生组合继承的语法糖
- ES5 组合继承:
8. 事件循环(Event Loop)宏任务与微任务
⚠️ 「UI 渲染」不是宏任务——它是浏览器在「微任务清空后、下一个宏任务前」择机执行的独立步骤;同理
requestAnimationFrame也不属于宏/微任务,挂在渲染步骤之前。这两个是最常见的错答点。
| 类型 | 包含 |
|---|---|
| 宏任务(MacroTask) | script(整体代码)、setTimeout、setInterval、setImmediate(Node)、I/O、MessageChannel、UI 事件回调 |
| 微任务(MicroTask) | Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node,优先级高于微任务) |
执行流程:执行一个宏任务 → 遇微任务入微队列 → 当前宏任务完清空整个微队列 → 浏览器可能进行 UI 渲染 → 取下一个宏任务 → 循环往复。
9. 深拷贝(含循环引用与边界类型)
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
if (hash.has(obj)) return hash.get(obj) // 解决循环引用
const cloneObj = new obj.constructor()
hash.set(obj, cloneObj)
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
10. new 操作符原理
function myNew(constructor, ...args) {
// 1. 创建空对象
const obj = {}
// 2. 空对象的原型指向构造函数的 prototype
Object.setPrototypeOf(obj, constructor.prototype)
// 3. 构造函数 this 指向新对象并执行
const result = constructor.apply(obj, args)
// 4. 构造函数返回对象则返回它,否则返回新对象
return result instanceof Object ? result : obj
}
11. Promise.all 实现
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) return reject(new TypeError('Arguments must be an array'))
const results = []
let count = 0
if (promises.length === 0) return resolve(results)
promises.forEach((promise, index) => {
Promise.resolve(promise).then(value => {
results[index] = value
count++
if (count === promises.length) resolve(results)
}).catch(reject)
})
})
}
12. 防抖(Debounce)与节流(Throttle)的核心区别
| 防抖(Debounce) | 节流(Throttle) | |
|---|---|---|
| 触发时机 | 停止触发 N ms 后才执行 | 每 N ms 内最多执行一次 |
| 典型场景 | 搜索框输入、resize 结束 | 滚动事件、mousemove、射击游戏子弹 |
⚠️ 节流实现要点:必须用
Date.now()时间戳比较(leading + trailing 双触发),而不是简单的setTimeout——后者无法保证 leading 立即触发。
三、React(2 题)
13. Virtual DOM 与 Diff 算法
- VDOM 本质:JS 对象,是对真实 DOM 的轻量级抽象
- 为什么快:JS 层面操作找最小变更,一次性批量更新真实 DOM,减少回流重绘
- Diff 三原则:
- 同级比较:不跨层级,O(n)
- 不同类型节点:直接删除旧子树重建
- 相同类型节点:递归比较属性 + 子节点
- key 的作用:识别节点可否复用/移动/删除——**
key应稳定且唯一,不要用数组索引(增删元素时索引会变,导致性能问题或状态错乱)
14. Hooks 原理与使用约束
- 为什么需要 Hooks:HOC / Render Props 嵌套地狱、Class 生命周期混杂无关逻辑、
this指向困扰、不利于 tree-shaking useStatevssetState:setState合并更新(浅合并);useState的set替换更新- React 18 之前:合成事件/生命周期中两者异步批处理;React 18 起自动批处理覆盖 Promise/setTimeout
useEffectvsuseLayoutEffect:useEffect浏览器绘制后异步执行(不阻塞渲染)useLayoutEffectDOM 变更后、绘制前同步执行(用于读/改 DOM 并立即生效)
- Hooks 原理:React 内部为每个组件维护链表存储 Hooks;调用顺序决定位置 → Hooks 不能在条件/循环/嵌套函数中调用
四、工程化(4 题)
15. Webpack Loader 与 Plugin 的区别
- Loader:转换器,处理非 JS/JSON 模块(
.css/.vue/ 图片),把它们转为有效模块 - Plugin:扩展器,监听 webpack 全生命周期广播的事件,通过钩子执行更广泛任务(打包优化、资源管理、环境变量注入)
16. HMR(热更新)原理
⚠️ 纠偏:早期
webpack-hot-middleware用的是 JSONP,现代webpack-dev-server已改为fetch/XHR 拉取 chunk(带webpackHotUpdate回调)。答 JSONP 会被认为停留在老版本。
- 文件监听:webpack 监听文件变化,重新编译打包,代码保存到内存
- 通知:
webpack-dev-server通过 WebSocket 与浏览器建立长连接,通知新模块hash - 请求更新:浏览器端 HMR runtime 根据
hash通过fetch/XHR 向服务器拉取hot-update.json(更新清单)+hot-update.js(增量模块代码),执行回调完成替换 - 模块替换:HMR runtime 执行模块替换,通知应用代码执行回调(
module.hot.accept),实现无刷新更新
17. Webpack 构建流程(七步)
- 初始化参数:合并配置文件与 Shell 参数
- 开始编译:初始化
Compiler对象,加载插件,执行run - 确定入口:根据
entry找所有入口文件 - 编译模块:从入口出发,调用 Loader 处理,再找依赖模块递归处理
- 完成模块编译:得到每个模块翻译后内容 + 依赖关系图
- 输出资源:组装成 chunk,每个 chunk 转文件加入输出列表
- 输出完成:确定输出路径与文件名,写入文件系统
18. TypeScript 核心:interface vs type + 工具类型
interfacevstype:interface可重复声明(声明合并),type不能interface用extends继承,type用交叉类型&type可定义联合类型 (|)、元组、基本类型别名——更灵活- 官方推荐优先
interface,无法满足需求时用type
- 工具类型实现:
type Partial<T> = { [P in keyof T]?: T[P] } type Pick<T, K extends keyof T> = { [P in K]: T[P] } type Readonly<T> = { readonly [P in keyof T]: T[P] } type Record<K extends keyof any, T> = { [P in K]: T }
五、浏览器与网络(3 题)
19. 从输入 URL 到页面显示的完整过程(八步)
- URL 解析:判断搜索关键字还是网址
- DNS 解析:浏览器缓存 → OS Hosts → 本地 DNS → 根/顶级/权威 DNS
- TCP 三次握手:建立可靠连接
- HTTP/HTTPS 请求:浏览器发请求报文;HTTPS 多 TLS 握手
- 服务器处理并返回响应:状态码 + 响应头 + 响应体
- 浏览器解析响应:判断重定向等;HTML 开始解析渲染
- TCP 四次挥手(短连接);HTTP/1.1+ 默认
Connection: keep-alive长连接 - 浏览器渲染:HTML → DOM 树、CSS → CSSOM 树 → Render 树 → Layout → Paint → Composite
20. 浏览器缓存机制(强缓存 + 协商缓存)
- 强缓存:直接从本地缓存读,不与服务器通信
Expires(HTTP/1.0,绝对时间,受本地时间影响)Cache-Control: max-age=3600(HTTP/1.1,相对时间,优先级更高)
- 协商缓存:发请求让服务器判断缓存是否可用,可用返回 304 用本地缓存
Last-Modified+If-Modified-Since(秒级精度)ETag+If-None-Match(基于内容指纹,比 Last-Modified 更精确)
21. XSS 与 CSRF
- XSS(跨站脚本):恶意脚本注入网页,其他用户浏览时执行
- 防御:输入过滤 + 输出转义(HTML 实体编码)、CSP、
Cookie加HttpOnly
- 防御:输入过滤 + 输出转义(HTML 实体编码)、CSP、
- CSRF(跨站请求伪造):诱导用户访问恶意网站,利用其登录状态发起恶意请求
- 防御:Token 验证、验证
Referer、敏感操作二次验证(验证码)
- 防御:Token 验证、验证
六、性能(1 题)
22. Core Web Vitals(核心 Web 指标)
| 指标 | 含义 | 阈值 |
|---|---|---|
| LCP(Largest Contentful Paint) | 最大内容绘制(加载性能) | ≤ 2.5s |
| INP(Interaction to Next Paint) | 交互到下一次绘制(已 2024-03-12 取代 FID) | ≤ 200ms(第 75 百分位) |
| CLS(Cumulative Layout Shift) | 累积布局偏移(视觉稳定性) | ≤ 0.1 |
| FCP / TTI / TBT | 辅助指标 | — |
RUM(Real User Monitoring):用 Performance API 收集真实用户数据上报到监控平台(Sentry/自研);熟练使用 Chrome DevTools 的 Performance / Lighthouse / Network 面板进行瓶颈分析。
七、微前端(1 题)
23. 微前端核心价值、主流方案与核心问题
- 核心价值:
- 技术栈无关:不同团队用不同框架
- 独立开发部署:降低耦合、提升迭代速度
- 增量升级:老项目逐步重构而非重写
- 主流方案:
| 方案 | 隔离 | 体验 | 改造成本 | 适用 |
|---|---|---|---|---|
| iframe | 最强 | 差 | 低 | 异构老系统嵌入(体验扣分) |
| qiankun(single-spa + HTML Entry) | 好(JS 沙箱 + 样式隔离) | 好 | 中 | 存量渐进迁移首选 |
| Module Federation | 弱 | 好 | 中高 | 新建 / 同技术栈 / 运行时共享 |
| wujie(无界) | 很强 | 好 | 中 | 强隔离可接受 |
| Web Components | 强(Shadow DOM) | 中 | 高 | 组件级复用(不是发布权工具) |
- 核心问题:样式隔离(CSS Module / Shadow DOM / BEM)、JS 沙箱(Proxy 沙箱 / 快照沙箱)、应用通信(全局事件总线 / URL 传参 / 共享状态)
完整微前端工程实战(含 15 条坑、迁移六阶段、组件库 12 坑)见 微前端迁移与自研组件库实战 与 智慧中台项目。
八、手写代码精选(4 题)
24. 手写 Event Bus(发布-订阅)
class EventBus {
constructor() { this.events = {} }
on(event, listener) {
(this.events[event] || (this.events[event] = [])).push(listener)
}
emit(event, ...args) {
(this.events[event] || []).forEach(listener => listener(...args))
}
off(event, listener) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(l => l !== listener)
}
once(event, listener) {
const wrapper = (...args) => {
listener(...args)
this.off(event, wrapper)
}
this.on(event, wrapper)
}
}
25. 手写 compose(函数组合)
const compose = (...funcs) => {
if (funcs.length === 0) return arg => arg
if (funcs.length === 1) return funcs[0]
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
26. 手写 Redux 的 createStore
function createStore(reducer, preloadedState, enhancer) {
if (enhancer) return enhancer(createStore)(reducer, preloadedState)
let state = preloadedState
let listeners = []
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listener => listener())
return action
}
const subscribe = (listener) => {
listeners.push(listener)
return () => { listeners = listeners.filter(l => l !== listener) }
}
dispatch({ type: '@@redux/INIT' })
return { getState, dispatch, subscribe }
}
27. Promise 手写核心(链式调用)
- then 链式调用:每次返回新的 Promise,状态由上个 then 的返回值决定
- 返回普通值:直接 resolve
- 返回 Promise:等待其完成
- 抛出错误:直接 reject
- 完整实现涉及:状态机(pending/fulfilled/rejected)、异步任务(
process.nextTick/微任务)、值穿透(then 的参数不是函数时忽略)
九、软技能(1 题)
28. STAR 法则回答「技术难题」
- S(情境):项目背景、时间压力、团队规模
- T(任务):你需要解决的具体技术问题(用一句话说清)
- A(行动):调研方案 → 设计 → 编码 → 测试 → 复盘(讲你做了什么、为什么这么做,不要只讲结果)
- R(结果):用数据说话——性能提升百分比、Bug 数下降、团队采纳度
关键点:
- 不要编造业务增长数字(面试官会追问口径)——研发效能数据最可信(自己的流水线出来的)
- 必须主动补「代价/边界」——最容易拉开档次的地方;只会吹收益的人一看就没真扛过
附:通用速记卡
| 问题 | 一句话回答 |
|---|---|
| 性能优化第一步是什么? | 先定预算再优化——没目标就优化叫瞎忙;预算必须能进 CI 才算可信 |
| React Hooks 为什么不能在条件中调用? | React 内部按调用顺序维护 Hooks 链表,条件调用会破坏顺序对应关系 |
| 微前端解决了什么问题? | 多团队独立发布权(组织协作问题),不是性能问题 |
Promise.all 失败时怎么处理? | 任意一个 reject 即整体 reject,且不等待其他进行中的 Promise |
| 强缓存 vs 协商缓存选谁? | 内容稳定用强缓存(Cache-Control);常变动用协商缓存(ETag 更精确) |
完整 130 题(带 12 张图解)见源文档
前端题库/前端面试真题130题完整答案.md;通用考点详答见前端题库/index.md;AI/Agent 面试真题见 AI 面试精选;系统设计与场景题见 系统设计精选。