2026前端面试题全集(初级→高级)— 基于Boss直聘/牛客网/掘金高频考点
2026前端面试题全集(初级→高级)— 基于Boss直聘/牛客网/掘金高频考点
整理来源:Boss直聘前端JD、牛客网面经、掘金大厂题库(京东/字节/华为/荣耀)、面试鸭
适用岗位:前端开发工程师 / 高级前端工程师 / 前端专家
技术栈:Vue 3 + React + TypeScript + Pinia + Webpack/Vite
一、初级:HTML/CSS 基础
Q1: HTML语义化标签有哪些?为什么重要?
答案:
语义化标签:<header> <nav> <main> <article> <section> <aside> <footer> <time> <figure> 等。
- SEO友好:搜索引擎能更好理解页面结构
- 无障碍访问:屏幕阅读器可准确解析内容
- 代码可读性:开发者更容易维护
- 移动端适配:有助于响应式设计
Q2: Flex布局和Grid布局的区别?
| 特性 | Flex | Grid |
|---|---|---|
| 维度 | 一维(行或列) | 二维(行和列) |
| 适用场景 | 导航栏/列表/居中 | 整体页面布局/复杂网格 |
| 对齐方式 | justify-content / align-items | justify-items / align-items / place-items |
| 子项控制 | flex-grow/shrink/basis | grid-column/row |
| 学习曲线 | 平缓 | 较陡 |
Q3: 实现垂直居中的方法有哪些?(至少5种)
- Flexbox:
display:flex; align-items:center; justify-content:center - Grid:
display:grid; place-items:center - 绝对定位+transform:
position:absolute; top:50%; left:50%; transform:translate(-50%,-50%) - 绝对定位+margin:
position:absolute; top:0;bottom:0;left:0;right:0; margin:auto - table-cell:
display:table-cell; vertical-align:middle; text-align:center - line-height: 单行文本
line-height等于容器高度
Q4: CSS动画和transition的区别?
| 特性 | transition | animation |
|---|---|---|
| 触发条件 | 需要状态变化(hover等) | 可自动播放 |
| 循环 | 不支持 | 支持(animation-iteration-count) |
| 关键帧 | 只有开始/结束 | 多个关键帧(@keyframes) |
| 中间状态 | 自动计算 | 完全控制 |
| 适用场景 | 简单过渡效果 | 复杂动画序列 |
Q5: 什么是BFC?如何创建?有什么应用场景?
答案: BFC(块级格式化上下文)是独立的渲染区域,内部布局不影响外部。
创建方式:overflow非visible / float非none / position:absolute/fixed / display:inline-block/flex/grid
应用:防止外边距重叠 / 清除浮动 / 两栏自适应布局
Q6: 盒模型是什么?box-sizing有何作用?
答案: 标准盒模型 width/hight=content;IE盒模型 width/hight=content+padding+border。box-sizing:border-box 统一为IE盒模型,更符合直觉。
二、初级→中级:JavaScript 核心
Q7: var / let / const 的区别?
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 提升+初始化为undefined | 提升但不初始化(TDZ) | 提升但不初始化(TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 暂时性死区 | 无 | 有 | 有 |
Q8: 什么是闭包?给出实际使用场景。
答案: 闭包就是能读取其他函数内部变量的函数,本质是“函数 + 定义环境”的组合。
场景:
-
数据私有化:模块模式,如计数器
createCounter() -
函数柯里化:
curry(add)(1)(2)(3) -
防抖/节流:保存timer变量
-
事件处理:循环中绑定事件保存正确的索引
缺点:内存泄漏风险 — 不再使用的变量无法被GC回收,需手动置null释放。优点:保持状态,避免污染,数据私有化
Q9: 原型链是什么?如何访问对象的原型?
**答案:**原型链是 JavaScript 中对象通过内部 [[Prototype]] 链式连接其原型对象,用于属性/方法继承和查找的机制。每个对象都有 __proto__ 指向其构造函数的 prototype。
function Person(name) { this.name = name }
Person.prototype.sayHi = function() { console.log(this.name) }
const p = new Person('Tom')
p.__proto__ === Person.prototype // true
Person.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // 原型链终点
Object.getPrototypeOf(obj)— 标准方法obj.__proto__— 非标准但广泛支持obj.constructor.prototype— 不可靠(constructor可被修改)
Q10: this的指向规则?call/apply/bind的区别?
答案:
this指向:默认绑定(window/global) → 隐式绑定(对象方法) → 显式绑定(call/apply/bind) → new绑定 → 箭头函数(继承外层)
| 方法 | 调用方式 | 参数 | 返回值 |
|---|---|---|---|
| call | 立即执行 | 逐个参数 | 函数返回值 |
| apply | 立即执行 | 数组参数 | 函数返回值 |
| bind | 返回新函数 | 逐个参数 | 新函数 |
Q11: 事件循环(Event Loop)— 宏任务与微任务
答案:
执行顺序:同步代码 → 微任务队列(Promise.then/MutationObserver/queueMicrotask) → 宏任务队列(setTimeout/setInterval/I/O/UI渲染)
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// 输出: 1 4 3 2
考点:requestAnimationFrame 在渲染前执行,不属于宏/微任务。async/await 中 await 后面的代码相当于 .then()。
Q12: Promise.all / Promise.race / Promise.allSettled / Promise.any 的区别?
| 方法 | 行为 | 返回时机 |
|---|---|---|
| Promise.all | 全部成功才成功 | 任一失败立即reject |
| Promise.race | 竞速 | 第一个完成的(无论成功/失败) |
| Promise.allSettled | 全部完成 | 等所有完成,返回每项状态 |
| Promise.any | 任一成功即成功 | 第一个成功的;全部失败才reject |
Q13: 深拷贝 vs 浅拷贝?手写深拷贝。
答案:
浅拷贝只复制第一层(Object.assign / 展开运算符 ...),深拷贝需要递归处理所有层级。
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj) // 循环引用
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
const clone = Array.isArray(obj) ? [] : {}
map.set(obj, clone)
for (const key in obj) {
if (obj.hasOwnProperty(key)) clone[key] = deepClone(obj[key], map)
}
return clone
}
JSON.parse(JSON.stringify()) 的局限:无法处理函数/undefined/Symbol/Date/RegExp/循环引用。
Q14: 防抖(Debounce)和节流(Throttle)的区别及实现?
答案:
- 防抖:连续触发只执行最后一次(搜索框输入)
- 节流:固定时间间隔只执行一次(滚动事件)
// 防抖
function debounce(fn, delay = 300) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// 节流
function throttle(fn, interval = 300) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= interval) {
fn.apply(this, args)
lastTime = now
}
}
}
Q15: ES6+常用新特性有哪些?
答案:
let/const块级作用域- 箭头函数(无自己的this/arguments)
- 模板字符串 `${}`
- 解构赋值
const {a, b} = obj - 展开运算符
... - Promise / async/await
- Class语法糖
- 模块化
import/export - Map / Set / WeakMap / WeakSet
- Proxy / Reflect
- 可选链
?./ 空值合并??
三、中级:TypeScript
Q16: TypeScript中 interface 和 type 的区别?
| 特性 | interface | type |
|---|---|---|
| 声明合并 | 支持(同名自动合并) | 不支持 |
| 继承方式 | extends | & 交叉类型 |
| 可定义类型 | 对象类型为主 | 联合类型/元组/基本类型别名 |
| 扩展性 | 更开放 | 更精确 |
| 建议:定义对象结构优先用interface,需要联合类型/映射类型时用type。 |
Q17: TypeScript泛型是什么?给出使用场景。
答案: 泛型是类型参数化,让函数/类/接口能处理多种类型。
// 泛型函数
function identity<T>(arg: T): T { return arg }
// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key] }
// 泛型接口
interface ApiResponse<T> { code: number; data: T }
// 泛型工具类型
type Partial<T> = { [P in keyof T]?: T[P] }
type Pick<T, K extends keyof T> = { [P in K]: T[P] }
Q18: TypeScript中常用的工具类型有哪些?
答案:
Partial<T>— 所有属性变为可选Required<T>— 所有属性变为必选Readonly<T>— 所有属性变为只读Pick<T, K>— 从T中挑选部分属性Omit<T, K>— 从T中排除部分属性Record<K, T>— 构造K为键T为值的对象类型Exclude<T, U>— 从联合类型排除Extract<T, U>— 从联合类型提取ReturnType<T>— 获取函数返回类型
四、中级:网络与浏览器
Q19: HTTP状态码有哪些?分别代表什么?
| 状态码 | 含义 | 示例 |
|---|---|---|
| 200 | 成功 | 正常请求 |
| 301 | 永久重定向 | 域名迁移 |
| 302 | 临时重定向 | 登录跳转 |
| 304 | 未修改(缓存) | 协商缓存命中 |
| 400 | 请求错误 | 参数有误 |
| 401 | 未授权 | 未登录 |
| 403 | 禁止访问 | 无权限 |
| 404 | 未找到 | 资源不存在 |
| 500 | 服务器错误 | 后端报错 |
| 502 | 网关错误 | 上游服务异常 |
| 503 | 服务不可用 | 服务器过载 |
Q20: 什么是跨域?解决方案有哪些?
答案: 浏览器同源策略限制不同源(协议+域名+端口)之间的请求。
解决方案:
- CORS(主流):服务端设置
Access-Control-Allow-Origin - 代理:开发环境
devServer.proxy;生产环境 Nginx 反向代理 - JSONP(仅GET):利用
<script>标签不受同源限制 - postMessage:iframe跨域通信
- WebSocket:不受同源策略限制
- Nginx反向代理:生产环境最优方案
Q21: 浏览器缓存策略(强缓存 vs 协商缓存)?
答案:
| 类型 | HTTP头 | 状态码 | 是否请求服务器 |
|---|---|---|---|
| 强缓存 | Cache-Control / Expires | 200 (from cache) | 否 |
| 协商缓存 | ETag+If-None-Match / Last-Modified+If-Modified-Since | 304 | 是 |
Cache-Control 常用值:max-age=秒 no-cache(协商) no-store(不缓存) public/private
Q22: 浏览器渲染页面的过程?
答案:
- 解析HTML → DOM树
- 解析CSS → CSSOM树
- DOM + CSSOM → Render树
- 布局(Layout/Reflow):计算每个节点位置和大小
- 绘制(Paint):将像素绘制到屏幕
- 合成(Composite):GPU合成图层
重排(Reflow):修改几何属性触发 → 代价高
重绘(Repaint):修改外观属性 → 代价较低
合成:transform/opacity → 代价最低
Q23: localStorage / sessionStorage / cookie 的区别?
| 特性 | cookie | localStorage | sessionStorage |
|---|---|---|---|
| 容量 | ~4KB | ~5MB | ~5MB |
| 有效期 | 可设置过期时间 | 永久(手动删除) | 关闭标签页清除 |
| 与服务器通信 | 自动携带在HTTP头 | 不自动发送 | 不自动发送 |
| 作用域 | 同源+path | 同源 | 同源+同标签页 |
| 应用场景 | 登录态/用户标识 | 持久化配置 | 临时表单数据 |
Q24: 前端安全:XSS和CSRF是什么?如何防范?
答案:
XSS(跨站脚本攻击):注入恶意脚本到页面
- 存储型:恶意代码存到数据库
- 反射型:通过URL参数注入
- DOM型:修改DOM环境
防范:输入过滤/输出转义、CSP(内容安全策略)、HttpOnly Cookie
CSRF(跨站请求伪造):诱导用户点击链接发起非本意请求
防范:SameSite Cookie、CSRF Token、Referer验证、验证码
五、中级→高级:Vue 深度
Q25: Vue2 响应式原理 vs Vue3 响应式原理(深入对比)
答案:
Vue2 — Object.defineProperty:
- 递归遍历对象所有属性,给每个属性添加getter/setter
- 无法检测新增/删除属性 → 需要
Vue.set/delete - 数组需重写7个变异方法(push/pop/shift/unshift/splice/sort/reverse)
- 初始化时递归遍历,性能开销大
Vue3 — Proxy:
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) trigger(target, key) // 触发更新
return result
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey) trigger(target, key)
return result
}
})
}
Proxy优势:原生监听数组/对象增删、惰性代理(用到才代理)、支持Map/Set/WeakMap
Q26: Vue3 Composition API 和 React Hooks 有什么异同?
| 维度 | Composition API | React Hooks |
|---|---|---|
| 依赖追踪 | Proxy自动追踪 | 手动声明依赖数组 |
| 调用次数 | setup()执行一次 | 每次渲染都执行 |
| 条件调用 | 无限制 | 不能在条件/循环中使用 |
| 生命周期 | onMounted/onUnmounted等 | useEffect模拟 |
| 逻辑复用 | 组合式函数(composables) | 自定义Hooks |
Q27: Vue的Diff算法原理及优化策略?
答案:
Vue3采用快速Diff算法(双端比较+最长递增子序列):
- 头头比较:新旧头节点相同 → patch
- 尾尾比较:新旧尾节点相同 → patch
- 头尾交叉:旧头=新尾 → 移动节点到尾部
- 尾头交叉:旧尾=新头 → 移动节点到头部
- 中间乱序:使用key建立索引映射,用最长递增子序列算法找出不需移动的节点,创建/删除/移动其余节点
Vue3编译优化:
- 静态提升:静态节点提升到渲染函数外
- 补丁标记(Patch Flags):只比较动态内容
- Block Tree:收集动态子节点,跳过静态节点diff
- 靶向更新(Targeted Update):通过动态节点的扁平数组精确更新
Q28: computed 和 watch 的区别?使用场景?
| 特性 | computed | watch |
|---|---|---|
| 缓存 | 有(依赖不变不重新计算) | 无 |
| 返回值 | 必须有返回值 | 不需要 |
| 异步 | 不支持 | 支持 |
| 使用场景 | 派生状态、模板绑定 | 数据变化后的副作用/异步操作 |
| 写法 | 函数或{get,set}对象 | 直接监听ref/reactive/数组 |
Q29: v-if 和 v-show 的区别?
- v-if:条件为假时不渲染DOM元素,切换开销大(销毁/重建),适合运行时条件很少改变
- v-show:始终渲染,仅切换
display:none,初始渲染开销大,适合频繁切换
Q30: Vue3 生命周期钩子有哪些?对应Vue2是什么?
| Vue2 (Options) | Vue3 (Composition) | 说明 |
|---|---|---|
| beforeCreate | setup() | 组件创建前 |
| created | setup() | 组件创建完成 |
| beforeMount | onBeforeMount | 挂载前 |
| mounted | onMounted | 挂载完成 |
| beforeUpdate | onBeforeUpdate | 更新前 |
| updated | onUpdated | 更新完成 |
| beforeDestroy | onBeforeUnmount | 销毁前 |
| destroyed | onUnmounted | 销毁完成 |
| errorCaptured | onErrorCaptured | 错误捕获 |
Q31: Vue组件间通信方式完整梳理?
- Props/Emits — 父子标准通信
- v-model — 双向绑定语法糖(Vue3可多个v-model)
- ref + defineExpose — 父访问子组件实例
- provide/inject — 跨层级注入(支持响应式)
- Pinia/Vuex — 全局状态管理
- mitt/tiny-emitter — 事件总线
- $attrs — 属性透传
- Router参数 — 路由传参
Q32: Pinia 深入 — 和 Vuex 的对比及核心用法?
答案:
为什么 Pinia 取代 Vuex:
- 没有 mutations(直接 actions 修改 state)
- 完整的 TypeScript 类型推导
- 扁平化 stores(不需要嵌套 modules)
- 支持组合式 store 写法(
setup store) - 体积更小,devtools 支持更好
// Option Store
export const useUserStore = defineStore('user', {
state: () => ({ name: '', token: '' }),
getters: {
isLoggedIn: (state) => !!state.token
},
actions: {
async login(credentials) {
const res = await api.login(credentials)
this.token = res.token
this.name = res.name
}
}
})
// Setup Store (组合式写法)
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, double, increment }
})
六、中级→高级:React 深度
Q33: React虚拟DOM和Diff算法原理?
答案:
虚拟DOM是用JS对象描述真实DOM结构,通过对比新旧VDOM树找出最小变更。
React Diff三大策略:
- Tree Diff:只比较同层级节点,跨层级操作直接删除+新建
- Component Diff:同类型组件继续diff,不同类型直接替换
- Element Diff:通过key唯一标识,进行插入/移动/删除
Q34: React Fiber架构是什么?解决了什么问题?
答案:
Fiber是React 16引入的新的协调引擎核心,是虚拟DOM的重新实现。
解决的问题:
- 可中断渲染:Fiber树允许将渲染工作拆分成小单元,可暂停/恢复
- 优先级调度:高优先级更新(用户输入/动画)可打断低优先级渲染
- 时间切片(Time Slicing):每帧留出时间给浏览器处理UI事件
Fiber节点结构:{ type, key, stateNode, child, sibling, return, alternate, effectTag }
Q35: React Hooks 核心 — useState / useEffect / useMemo / useCallback?
答案:
- useState:函数组件状态管理,基于链表存储,按调用顺序匹配
- useEffect:副作用处理。第二个参数是依赖数组,空数组 = 仅mount执行,无数组 = 每次渲染执行
- 清理函数:return的函数在组件卸载/依赖变化前执行
- useMemo:缓存计算结果,依赖不变不重新计算
- useCallback:缓存函数引用,避免子组件不必要的重渲染
useEffect vs useLayoutEffect:
- useEffect 在浏览器绘制后异步执行,不阻塞渲染
- useLayoutEffect 在DOM变更后、浏览器绘制前同步执行,阻塞渲染
Q36: React.memo / PureComponent / shouldComponentUpdate 的区别?
- shouldComponentUpdate:Class组件手动比较
- PureComponent:自动浅比较props和state
- React.memo:函数组件的高阶组件版本,浅比较props
Q37: Redux 核心概念和中间件原理?
答案:
三大原则:单一数据源 / State只读 / 纯函数reducer修改
中间件原理(以redux-thunk为例):拦截dispatch,使action可以是函数,在函数内处理异步逻辑后再dispatch普通action。
// redux-thunk 简化实现
const thunk = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState)
}
return next(action)
}
Q38: React 18 新特性有哪些?
- 并发模式(Concurrent Mode):可中断渲染
- 自动批处理(Automatic Batching):多个setState合并为一次渲染
- Suspense改进:支持服务端渲染
- useId:生成唯一ID
- useTransition / useDeferredValue:标记低优先级更新
- createRoot 替代 ReactDOM.render
七、高级:性能优化
Q39: 首屏加载速度优化方案(综合)?
- 资源压缩:Gzip/Brotli压缩、JS/CSS压缩、图片WebP格式
- 代码分割:路由懒加载
() => import()、组件按需加载 - CDN加速:静态资源CDN分发
- 缓存策略:强缓存+协商缓存、Service Worker离线缓存
- SSR/SSG:服务端渲染或静态生成
- 预加载:
<link rel="preload"><link rel="prefetch"> - 关键CSS内联:首屏CSS直接内联到HTML
- 骨架屏/loading:提升感知体验
- Tree Shaking:移除未使用代码
- HTTP/2多路复用:减少请求数限制
Q40: 虚拟列表(Virtual List)是什么?如何实现?
答案: 只渲染可视区域内的DOM节点,适用于万级数据列表。
核心参数:容器高度 / 每项高度 / 可视数量 = 容器高度 ÷ 每项高度
实现要点:
- 监听scroll事件计算
startIndex = Math.floor(scrollTop / itemHeight) - 仅渲染
items.slice(startIndex, startIndex + visibleCount) - 通过
padding-top或transform:translateY占位隐藏区域 - 不等高项需要通过预估高度+实测高度动态调整
Q41: 长列表渲染的其他优化方案?
- 分页加载:传统后端分页
- 无限滚动:
IntersectionObserver监听触底加载 - 时间分片(Time Slicing):
requestIdleCallback拆分长任务 - Web Worker:复杂计算移出主线程
Q42: 前端监控体系如何搭建?
答案:
错误监控:
- JS错误:
window.onerror/window.addEventListener('error') - Promise错误:
window.addEventListener('unhandledrejection') - 框架级:
Vue.config.errorHandler/ React ErrorBoundary - 资源加载错误:
addEventListener('error', ..., true)捕获阶段
性能监控:
- FCP(首次内容绘制) / LCP(最大内容绘制) —
PerformanceObserver - FID(首次输入延迟) / CLS(累积布局偏移) — Web Vitals
- 自定义指标:API耗时、页面完全加载时间
工具:Sentry / 阿里云ARMS / 自建上报系统
八、高级:前端工程化
Q43: Vite 为什么比 Webpack 快?
答案:
Vite核心优势 — 基于浏览器原生ESM:
- 开发阶段:无需打包,利用浏览器原生ES Module按需加载,启动极快
- HMR:只失效修改文件相关的模块,不重新打包整个应用
- 预构建:用esbuild预构建node_modules中的依赖,esbuild是Go编写的,比JS打包器快10-100倍
- 生产环境:使用Rollup打包,充分利用Tree Shaking
Webpack慢的原因:需要先将所有模块打包成一个或多个bundle,项目越大打包越慢。
Q44: Webpack的核心概念:Loader和Plugin的区别?
| 特性 | Loader | Plugin |
|---|---|---|
| 作用 | 转换文件(翻译官) | 扩展功能(构建流程介入) |
| 本质 | 函数 | 类(含apply方法) |
| 配置位置 | module.rules | plugins数组 |
| 示例 | css-loader/style-loader/babel-loader | HtmlWebpackPlugin/MiniCssExtractPlugin |
Q45: Webpack的Tree Shaking原理?何时会失效?
答案: Tree Shaking基于ES Module的静态结构,在编译阶段分析import/export,移除未被引用的代码。
失效场景:
- 使用了CommonJS(
require),无法静态分析 - 副作用代码(sideEffects)
- 动态import
- babel配置将ESM转为CommonJS(需设置
modules: false)
Q46: Webpack性能优化手段?
- 缩小构建范围:exclude/include、resolve.alias、noParse
- 缓存:cache-loader/babel-loader cacheDirectory/Webpack5持久化缓存
- 多线程:thread-loader/happypack
- DLL:预编译不常变动的库(Webpack5可用cache替代)
- 代码分割:splitChunks配置,分离vendor/business
- 压缩并行:terser-webpack-plugin开启parallel
Q47: CI/CD流水线在前端项目中如何设计?
答案:
代码提交 → Lint检查 → 单元测试 → 构建(build) → 部署(deploy)
- Lint:ESLint + Prettier + Stylelint + Commitlint
- 测试:Vitest/Jest单元测试 → Cypress/Playwright E2E
- 构建:多环境配置(dev/staging/prod)、产物上传CDN
- 部署:蓝绿部署/灰度发布、静态资源CDN刷新
- 工具:GitHub Actions / GitLab CI / Jenkins
九、高级:架构与设计
Q48: 如何设计一个可维护的大型Vue3项目?
答案:
目录规范:
src/
├── api/ # 接口封装(按模块)
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── base/ # 基础组件(Button/Input)
│ └── business/ # 业务组件
├── composables/ # 组合式函数
├── layouts/ # 布局组件
├── pages/ # 页面
├── router/ # 路由(按模块拆分)
├── stores/ # Pinia stores
├── styles/ # 全局样式/变量
├── types/ # TS类型定义
└── utils/ # 工具函数
规范约束:ESLint + Prettier + Husky + lint-staged + Commitizen
状态管理:全局状态→Pinia;跨组件→provide/inject;局部状态→组件内
Q49: 微前端架构的应用场景和主流方案?
答案:
场景:多团队协作、遗留系统渐进式重构、技术栈异构
方案对比:
| 方案 | 优点 | 缺点 |
|---|---|---|
| iframe | 天然隔离 | URL不同步/通信复杂 |
| qiankun | 阿里出品/生态完善 | 基于single-spa |
| Module Federation | Webpack5原生 | 仅限Webpack |
| Micro-app | 京东出品/上手简单 | 社区较新 |
Q50: SPA单页应用的优缺点?如何优化SEO?
答案:
优点:用户体验流畅、前后端分离、减轻服务器压力
缺点:首屏慢、SEO不友好
SEO方案:
- SSR:Nuxt.js(Vue) / Next.js(React) — 服务端渲染
- SSG:静态站点生成,预渲染HTML
- 预渲染:prerender-spa-plugin对部分路由预渲染
- 动态渲染:对爬虫返回预渲染版本
十、高级:跨端开发
Q51: 跨端开发方案对比(uni-app / Taro / React Native / Flutter / Electron)
| 方案 | 平台 | 语言/框架 | 性能 | 生态 |
|---|---|---|---|---|
| uni-app | H5/小程序/App | Vue | 中等 | 丰富 |
| Taro | H5/小程序/RN | React/Vue | 中等 | 丰富 |
| React Native | iOS/Android | React | 接近原生 | 丰富 |
| Flutter | iOS/Android/Web/Desktop | Dart | 高 | 增长中 |
| Electron | Windows/Mac/Linux | Web技术 | 中等 | 丰富 |
选型建议:小程序多用uni-app/Taro;移动端原生体验用Flutter/RN;桌面端用Electron
十一、手写代码专题(高频必考)
Q52: 手写 Promise(含完整 then/catch/resolve/reject)
class MyPromise {
constructor(executor) {
this.state = 'pending'; this.value = undefined; this.reason = undefined
this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'; this.value = value
this.onFulfilledCallbacks.forEach(cb => cb())
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'; this.reason = reason
this.onRejectedCallbacks.forEach(cb => cb())
}
}
try { executor(resolve, reject) } catch (error) { reject(error) }
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try { resolvePromise(promise2, onFulfilled(this.value), resolve, reject) }
catch (e) { reject(e) }
})
} else if (this.state === 'rejected') { /* 同理 */ }
else {
this.onFulfilledCallbacks.push(() => { /* 同理 */ })
this.onRejectedCallbacks.push(() => { /* 同理 */ })
}
})
return promise2
}
}
Q53: 手写防抖和节流(含立即执行版)
// 防抖-立即执行版
function debounce(fn, delay, immediate = false) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
if (immediate && !timer) fn.apply(this, args)
timer = setTimeout(() => {
if (!immediate) fn.apply(this, args)
timer = null
}, delay)
}
}
// 节流-时间戳+定时器版
function throttle(fn, interval) {
let lastTime = 0, timer = null
return function(...args) {
const now = Date.now()
const remaining = interval - (now - lastTime)
if (remaining <= 0) {
if (timer) { clearTimeout(timer); timer = null }
fn.apply(this, args); lastTime = now
} else if (!timer) {
timer = setTimeout(() => { fn.apply(this, args); lastTime = Date.now(); timer = null }, remaining)
}
}
}
Q54: 手写 EventEmitter(发布订阅)
class EventEmitter {
constructor() { this.events = {} }
on(event, listener) {
(this.events[event] || (this.events[event] = [])).push(listener)
return this
}
off(event, listener) {
if (!this.events[event]) return this
this.events[event] = this.events[event].filter(fn => fn !== listener)
return this
}
emit(event, ...args) {
(this.events[event] || []).forEach(fn => fn(...args))
}
once(event, listener) {
const wrapper = (...args) => { listener(...args); this.off(event, wrapper) }
this.on(event, wrapper)
return this
}
}
Q55: 手写 Promise.all / Promise.race
Promise.myAll = function(promises) {
const results = []; let count = 0
return new Promise((resolve, reject) => {
promises.forEach((p, i) => {
Promise.resolve(p).then(res => {
results[i] = res
if (++count === promises.length) resolve(results)
}).catch(reject)
})
})
}
Promise.myRace = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach(p => Promise.resolve(p).then(resolve, reject))
})
}
Q56: 手写 call / apply / bind
Function.prototype.myCall = function(context, ...args) {
context = context || window
const fnSymbol = Symbol()
context[fnSymbol] = this
const result = context[fnSymbol](...args)
delete context[fnSymbol]
return result
}
Function.prototype.myBind = function(context, ...args1) {
const self = this
return function(...args2) {
return self.apply(this instanceof self ? this : context, [...args1, ...args2])
}
}
Q57: 手写 LRU 缓存
class LRUCache {
constructor(capacity) { this.capacity = capacity; this.cache = new Map() }
get(key) {
if (!this.cache.has(key)) return -1
const value = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, value)
return value
}
put(key, value) {
if (this.cache.has(key)) this.cache.delete(key)
else if (this.cache.size >= this.capacity) {
this.cache.delete(this.cache.keys().next().value)
}
this.cache.set(key, value)
}
}
Q58: 手写数组扁平化 flat
function flat(arr, depth = 1) {
if (depth < 1) return arr
return arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur)
}, [])
}
Q59: 手写 instanceOf
function myInstanceOf(obj, constructor) {
let proto = Object.getPrototypeOf(obj)
while (proto) {
if (proto === constructor.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}
Q60: 手写 new 操作符
function myNew(constructor, ...args) {
const obj = Object.create(constructor.prototype)
const result = constructor.apply(obj, args)
return result instanceof Object ? result : obj
}
Q61: 手写带并发限制的异步调度器
class AsyncScheduler {
constructor(max) { this.max = max; this.queue = []; this.running = 0 }
add(task) {
return new Promise((resolve, reject) => {
const execute = () => {
this.running++
task().then(resolve, reject).finally(() => { this.running--; this.next() })
}
this.queue.push(execute)
this.next()
})
}
next() {
while (this.queue.length && this.running < this.max) this.queue.shift()()
}
}
十二:高频算法题
Q62: 有效的括号(栈)
function isValid(s) {
const stack = [], map = { '(': ')', '[': ']', '{': '}' }
for (const c of s) {
if (map[c]) stack.push(c)
else if (map[stack.pop()] !== c) return false
}
return stack.length === 0
}
Q63: 无重复字符的最长子串(滑动窗口)
function lengthOfLongestSubstring(s) {
const map = new Map(); let max = 0, left = 0
for (let right = 0; right < s.length; right++) {
if (map.has(s[right])) left = Math.max(left, map.get(s[right]) + 1)
map.set(s[right], right)
max = Math.max(max, right - left + 1)
}
return max
}
Q64: 两数之和(哈希表)
function twoSum(nums, target) {
const map = new Map()
for (let i = 0; i < nums.length; i++) {
const diff = target - nums[i]
if (map.has(diff)) return [map.get(diff), i]
map.set(nums[i], i)
}
}
Q65: 快速排序
function quickSort(arr) {
if (arr.length <= 1) return arr
const pivot = arr[0], left = [], right = []
for (let i = 1; i < arr.length; i++) {
arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i])
}
return [...quickSort(left), pivot, ...quickSort(right)]
}
Q66: 链表反转
function reverseList(head) {
let prev = null, curr = head
while (curr) {
const next = curr.next
curr.next = prev
prev = curr
curr = next
}
return prev
}
十三:场景题 / 系统设计
Q67: 如何设计一个前端组件库?
答案:
- 技术选型:Vue3/React + TypeScript + Vite/Rollup
- 目录结构:
packages/components+packages/theme+packages/utils - 按需加载:支持 tree-shaking,每个组件独立导出
- 主题系统:CSS变量 / Less/Sass变量
- 文档:VitePress/Storybook
- 测试:Vitest + Vue Test Utils / React Testing Library
- CI/CD:自动发布npm + changelog生成
Q68: 大文件上传如何实现?断点续传?
答案:
- 分片上传:File.slice() 切割文件为多个chunk
- 并发控制:限制同时上传的chunk数量
- 断点续传:通过spark-md5计算文件hash,后端返回已上传chunk列表
- 秒传:整个文件的hash在后端已存在则直接返回成功
- 进度展示:
已上传chunk数 / 总chunk数 - 失败重试:单个chunk失败自动重试
Q69: 如何设计一个前端权限系统?
答案:
- 页面级权限:路由守卫根据角色动态添加可访问路由
- 按钮级权限:自定义指令
v-permission控制显示/隐藏 - 数据级权限:接口层面限制
- 实现:登录后获取权限列表 → 存储Pinia → 路由守卫 + 指令控制
Q70: WebSocket 在实际项目中如何应用?断线重连策略?
答案:
应用场景:即时通讯、实时数据推送、协同编辑
心跳机制:定时发送ping,超时未收到pong则重连
重连策略:指数退避 — 1s → 2s → 4s → 8s → 最大30s
Q71: 前端如何处理大数据量(10万+)的导出?
答案:
- 前端方案:分批异步处理 + Web Worker
- 更优方案:后端流式导出,前端显示进度条
- Excel导出:使用
exceljs流式写入,避免一次性加载全部数据到内存
题库总量:71道高频面试题,涵盖 HTML/CSS → JavaScript核心 → TypeScript → 网络/浏览器 → Vue深度 → React深度 → 性能优化 → 工程化 → 架构设计 → 跨端 → 手写代码 → 算法 → 场景题
持续更新中 · 基于2025-2026年Boss直聘/牛客网/掘金面经汇总
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)