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种)

  1. Flexbox: display:flex; align-items:center; justify-content:center
  2. Grid: display:grid; place-items:center
  3. 绝对定位+transform: position:absolute; top:50%; left:50%; transform:translate(-50%,-50%)
  4. 绝对定位+margin: position:absolute; top:0;bottom:0;left:0;right:0; margin:auto
  5. table-cell: display:table-cell; vertical-align:middle; text-align:center
  6. 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/awaitawait 后面的代码相当于 .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: 什么是跨域?解决方案有哪些?

答案: 浏览器同源策略限制不同源(协议+域名+端口)之间的请求。
解决方案

  1. CORS(主流):服务端设置 Access-Control-Allow-Origin
  2. 代理:开发环境 devServer.proxy;生产环境 Nginx 反向代理
  3. JSONP(仅GET):利用 <script> 标签不受同源限制
  4. postMessage:iframe跨域通信
  5. WebSocket:不受同源策略限制
  6. 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: 浏览器渲染页面的过程?

答案:

  1. 解析HTML → DOM树
  2. 解析CSS → CSSOM树
  3. DOM + CSSOM → Render树
  4. 布局(Layout/Reflow):计算每个节点位置和大小
  5. 绘制(Paint):将像素绘制到屏幕
  6. 合成(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算法(双端比较+最长递增子序列):

  1. 头头比较:新旧头节点相同 → patch
  2. 尾尾比较:新旧尾节点相同 → patch
  3. 头尾交叉:旧头=新尾 → 移动节点到尾部
  4. 尾头交叉:旧尾=新头 → 移动节点到头部
  5. 中间乱序:使用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组件间通信方式完整梳理?

  1. Props/Emits — 父子标准通信
  2. v-model — 双向绑定语法糖(Vue3可多个v-model)
  3. ref + defineExpose — 父访问子组件实例
  4. provide/inject — 跨层级注入(支持响应式)
  5. Pinia/Vuex — 全局状态管理
  6. mitt/tiny-emitter — 事件总线
  7. $attrs — 属性透传
  8. 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三大策略

  1. Tree Diff:只比较同层级节点,跨层级操作直接删除+新建
  2. Component Diff:同类型组件继续diff,不同类型直接替换
  3. 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 新特性有哪些?

  1. 并发模式(Concurrent Mode):可中断渲染
  2. 自动批处理(Automatic Batching):多个setState合并为一次渲染
  3. Suspense改进:支持服务端渲染
  4. useId:生成唯一ID
  5. useTransition / useDeferredValue:标记低优先级更新
  6. createRoot 替代 ReactDOM.render

七、高级:性能优化

Q39: 首屏加载速度优化方案(综合)?

  1. 资源压缩:Gzip/Brotli压缩、JS/CSS压缩、图片WebP格式
  2. 代码分割:路由懒加载 () => import()、组件按需加载
  3. CDN加速:静态资源CDN分发
  4. 缓存策略:强缓存+协商缓存、Service Worker离线缓存
  5. SSR/SSG:服务端渲染或静态生成
  6. 预加载<link rel="preload"> <link rel="prefetch">
  7. 关键CSS内联:首屏CSS直接内联到HTML
  8. 骨架屏/loading:提升感知体验
  9. Tree Shaking:移除未使用代码
  10. HTTP/2多路复用:减少请求数限制

Q40: 虚拟列表(Virtual List)是什么?如何实现?

答案: 只渲染可视区域内的DOM节点,适用于万级数据列表。
核心参数:容器高度 / 每项高度 / 可视数量 = 容器高度 ÷ 每项高度
实现要点

  • 监听scroll事件计算 startIndex = Math.floor(scrollTop / itemHeight)
  • 仅渲染 items.slice(startIndex, startIndex + visibleCount)
  • 通过 padding-toptransform: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性能优化手段?

  1. 缩小构建范围:exclude/include、resolve.alias、noParse
  2. 缓存:cache-loader/babel-loader cacheDirectory/Webpack5持久化缓存
  3. 多线程:thread-loader/happypack
  4. DLL:预编译不常变动的库(Webpack5可用cache替代)
  5. 代码分割:splitChunks配置,分离vendor/business
  6. 压缩并行: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: 如何设计一个前端组件库?

答案:

  1. 技术选型:Vue3/React + TypeScript + Vite/Rollup
  2. 目录结构packages/components + packages/theme + packages/utils
  3. 按需加载:支持 tree-shaking,每个组件独立导出
  4. 主题系统:CSS变量 / Less/Sass变量
  5. 文档:VitePress/Storybook
  6. 测试:Vitest + Vue Test Utils / React Testing Library
  7. CI/CD:自动发布npm + changelog生成

Q68: 大文件上传如何实现?断点续传?

答案:

  1. 分片上传:File.slice() 切割文件为多个chunk
  2. 并发控制:限制同时上传的chunk数量
  3. 断点续传:通过spark-md5计算文件hash,后端返回已上传chunk列表
  4. 秒传:整个文件的hash在后端已存在则直接返回成功
  5. 进度展示已上传chunk数 / 总chunk数
  6. 失败重试:单个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直聘/牛客网/掘金面经汇总

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐