首页/文章/八股文

前端面试100题

2026-08-19
1391547 分钟
...

前端面试 100 题

适用方向:中高级前端 / React / Vue / Next.js / Nuxt / TypeScript / 工程化 / 实时通信 / Electron / Node.js / AI 应用前端
使用方式:优先掌握“标准回答”,再练“面试官追问”,最后把“结合你的简历怎么答”组织成自己的项目故事。

说明

  • “结合你的简历怎么答”只使用你简历中已经出现的项目与技术事实。
  • “标准回答 / 追问”属于通用前端知识总结,用来补足面试表达。
  • 建议不要逐字背诵,要能用自己的语言讲清楚“为什么”。

目录

  1. JavaScript 基础:1~15
  2. 异步、Promise、Event Loop:16~25
  3. TypeScript:26~35
  4. 浏览器与网络:36~47
  5. React:48~60
  6. Vue / Nuxt:61~68
  7. TanStack Query / 状态管理:69~75
  8. Next.js / SSR / 性能优化:76~84
  9. WebSocket / IM / 实时系统:85~91
  10. 工程化 / Electron / Node.js:92~97
  11. AI / Agent / SSE:98~100

一、JavaScript 基础

1. varletconst 有什么区别?

标准回答

  • var 是函数作用域,letconst 是块级作用域。
  • var 存在变量提升,声明会被提升并初始化为 undefined
  • let / const 也会创建绑定,但在声明前处于暂时性死区。
  • var 可以重复声明,let / const 不允许同作用域重复声明。
  • const 约束的是变量绑定不能重新赋值,不代表对象内部不可修改。
const user = { name: 'A' }
user.name = 'B' // 可以
// user = {}    // 不可以

面试官追问

为什么 let / const 也算“提升”?

因为进入作用域时绑定已经建立,但在声明执行前不能访问,因此会产生 TDZ。

2. 什么是闭包?

标准回答

闭包本质上是:函数在创建时会保留对其词法作用域的引用,即使外部函数已经执行结束,内部函数仍然可以访问这些变量。

function createCounter() {
  let count = 0
  return () => ++count
}

常见应用:

  • 私有变量
  • 防抖 / 节流
  • React Hooks
  • 缓存
  • 事件处理器
  • 函数柯里化

面试官追问

闭包一定会导致内存泄漏吗?

不会。只有当闭包长期持有不再需要的大对象引用,并且引用无法被释放时,才可能造成内存占用问题。

3. 说一下 JS 的作用域链。

标准回答

JavaScript 使用词法作用域。变量查找从当前作用域开始,找不到就沿外层词法环境逐层向上,直到全局作用域。

const a = 1

function foo() {
  const b = 2
  function bar() {
    console.log(a, b)
  }
  bar()
}

bar 能访问 ba,就是因为作用域链。

4. 什么是原型和原型链?

标准回答

对象访问属性时:

  1. 先查对象自身属性。
  2. 找不到则访问其原型。
  3. 再沿原型的原型继续查找。
  4. 最终到 Object.prototype
  5. 再往上是 null
function A() {}
const a = new A()

a.__proto__ === A.prototype
A.prototype.constructor === A

建议实际代码中使用:

Object.getPrototypeOf(a)

而不是依赖 __proto__

5. new 操作符做了什么?

标准回答

大致流程:

  1. 创建一个新对象。
  2. 将对象原型指向构造函数的 prototype
  3. 用新对象作为 this 执行构造函数。
  4. 如果构造函数显式返回对象,则返回它;否则返回新对象。
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype)
  const result = Constructor.apply(obj, args)

  return (
    result !== null &&
    (typeof result === 'object' || typeof result === 'function')
  ) ? result : obj
}

6. this 的指向规则有哪些?

标准回答

可以按优先级记:

  1. new 绑定
  2. call / apply / bind 显式绑定
  3. 对象方法调用时的隐式绑定
  4. 默认绑定
  5. 箭头函数没有自己的 this,从定义位置继承
const obj = {
  name: 'demo',
  foo() {
    console.log(this.name)
  }
}

const fn = obj.foo
fn()

fn() 已经脱离 obj 调用,因此不会再以 objthis

7. 箭头函数和普通函数有什么区别?

标准回答

箭头函数:

  • 没有自己的 this
  • 没有 arguments
  • 不能作为构造函数
  • 没有 prototype
  • this 取决于定义时的词法作用域

因此对象方法、构造函数等场景需要谨慎使用箭头函数。

8. callapplybind 有什么区别?

标准回答

  • call:立即执行,参数逐个传递。
  • apply:立即执行,参数以数组形式传递。
  • bind:返回一个绑定好 this 的新函数,不立即执行。

9. 深拷贝和浅拷贝有什么区别?

标准回答

浅拷贝只复制第一层引用。

const b = { ...a }

如果嵌套对象仍然是同一个引用,那么修改嵌套对象仍会互相影响。

深拷贝会递归复制整个对象结构。

浏览器环境优先考虑:

structuredClone(value)

但它也不能克隆函数、DOM 节点等所有对象。

10. JSON.parse(JSON.stringify()) 做深拷贝有什么问题?

标准回答

可能丢失或改变:

  • undefined
  • function
  • Symbol
  • BigInt
  • Date
  • Map
  • Set
  • 循环引用

因此不应该把 JSON 序列化当通用深拷贝方案。

11. ===== 有什么区别?

标准回答

=== 不做隐式类型转换。

== 会执行抽象相等比较,可能发生复杂的类型转换。

面试和工程实践中,一般优先使用 ===

经典例子:

[] == false        // true
null == undefined  // true
NaN === NaN        // false

12. 为什么 NaN !== NaN

标准回答

按照 IEEE 754 和 JavaScript 数值比较规则,NaN 与任何值都不相等,包括它自己。

判断推荐:

Number.isNaN(value)

13. Map 和普通对象有什么区别?

标准回答

Map

  • key 可以是任意类型。
  • 迭代顺序明确。
  • size
  • 高频增删查场景语义更清晰。

普通对象:

  • 更适合描述结构化数据。
  • 天然支持 JSON 序列化。
  • key 主要是字符串 / Symbol。

14. Set 常见使用场景?

标准回答

  • 数组去重
  • 快速判断存在性
  • 集合运算
  • 维护唯一 id 集合
const unique = [...new Set(list)]

15. 防抖和节流有什么区别?

标准回答

防抖 debounce

连续触发时只在停止触发一段时间后执行一次。

适合:

  • 搜索输入
  • 表单校验
  • resize 后重新布局

节流 throttle

固定时间窗口最多执行一次。

适合:

  • scroll
  • mousemove
  • 高频状态采样

二、异步、Promise、Event Loop

16. Promise 有哪些状态?

标准回答

  • pending
  • fulfilled
  • rejected

状态只能从:

pending -> fulfilled
pending -> rejected

一旦确定就不能再次改变。

17. async 函数返回什么?

标准回答

async 函数一定返回 Promise。

async function foo() {
  return 1
}

等价理解为:

function foo() {
  return Promise.resolve(1)
}

18. await 做了什么?

标准回答

await 会暂停当前 async 函数后续代码,将后续逻辑安排为 Promise continuation。

async function foo() {
  console.log(1)
  await Promise.resolve()
  console.log(2)
}

console.log(2) 会在后续微任务阶段继续执行。

19. Event Loop 是什么?

标准回答

浏览器执行模型可以简化为:

同步任务

调用栈清空

执行微任务队列

浏览器可能进行渲染

执行下一轮宏任务

常见微任务:

  • Promise.then
  • queueMicrotask

常见任务:

  • setTimeout
  • DOM Event
  • MessageChannel

20. 下面代码输出顺序是什么?

console.log(1)

setTimeout(() => console.log(2))

Promise.resolve().then(() => console.log(3))

console.log(4)

标准回答

1
4
3
2

原因:

  • 同步先执行。
  • Promise 回调是微任务。
  • setTimeout 是后续任务。

21. 再解释一道 async/await 顺序题。

async function foo() {
  console.log(1)
  await Promise.resolve()
  console.log(2)
}

console.log(3)
foo()
Promise.resolve().then(() => console.log(4))
console.log(5)

标准回答

3
1
5
2
4

核心是理解 await 后面的逻辑作为后续微任务继续执行。

22. Promise.allPromise.allSettled 区别?

标准回答

Promise.all

  • 全部成功才成功。
  • 任意一个 reject,会立即返回 rejected Promise。

Promise.allSettled

  • 等所有 Promise 都结束。
  • 返回每一项成功 / 失败状态。

适合批量任务收集结果。

23. Promise.racePromise.any 区别?

标准回答

race

  • 谁最先 settle 就采用谁的结果。
  • 成功失败都算。

any

  • 谁最先 fulfilled 就采用谁。
  • 全部失败才 reject。

24. 怎么做请求并发控制?

标准回答

核心思想是维护固定数量 worker:

async function runWithLimit(tasks, limit) {
  const results = new Array(tasks.length)
  let index = 0

  async function worker() {
    while (true) {
      const current = index++
      if (current >= tasks.length) return

      results[current] = await tasks[current]()
    }
  }

  await Promise.all(
    Array.from({ length: limit }, () => worker())
  )

  return results
}

面试时重点说:

  • 限制同时运行任务数。
  • 不阻塞后续任务。
  • 需要考虑错误处理和取消。

25. 如何取消一个请求?

标准回答

Fetch 可以使用 AbortController

const controller = new AbortController()

fetch('/api/data', {
  signal: controller.signal
})

controller.abort()

在 React 搜索、页面切换、竞态请求等场景很常见。

三、TypeScript

26. interfacetype 的区别?

标准回答

都可以描述对象结构。

interface

  • 更适合对象 / 类的契约。
  • 支持 declaration merging。

type

  • 可以表达 union、intersection、primitive alias 等更多类型组合。

不要机械地认为某一个一定“更好”。

27. anyunknown 区别?

标准回答

any 基本关闭类型检查。

unknown 表示“暂时不知道是什么”,使用前必须缩窄。

function foo(value: unknown) {
  if (typeof value === 'string') {
    value.toUpperCase()
  }
}

所以 unknown 更安全。

28. never 有什么用?

标准回答

never 表示永远不会出现的值。

常见:

  • 永远抛错的函数。
  • 无限循环。
  • Exhaustive Check。
function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`)
}

29. keyof 是什么?

type User = {
  id: number
  name: string
}

type UserKey = keyof User

标准回答

得到:

'id' | 'name'

30. typeof 在 TypeScript 中有什么作用?

标准回答

可以从现有变量推导类型:

const config = {
  api: '/api',
  retry: 3
}

type Config = typeof config

31. 泛型解决什么问题?

标准回答

泛型用于在“保留类型信息”的同时提高代码复用。

function identity<T>(value: T): T {
  return value
}

相比 any,泛型不会丢掉输入输出之间的类型关系。

32. 解释这段代码。

function getProperty<T, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key]
}

标准回答

  • T 表示对象类型。
  • K extends keyof T 限制 key 必须属于对象合法 key。
  • 返回值 T[K] 能准确推导属性类型。

33. 手写 Partial<T>

type MyPartial<T> = {
  [K in keyof T]?: T[K]
}

面试官追问

类似地准备:

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

34. 什么是条件类型?

标准回答

type IsString<T> = T extends string ? true : false

条件类型可以根据类型关系生成不同结果。

高级追问通常会涉及:

  • distributive conditional types
  • infer

35. infer 是什么?

标准回答

infer 用于在条件类型中推断内部类型。

type MyReturnType<T> =
  T extends (...args: any[]) => infer R
    ? R
    : never

四、浏览器与网络

36. 从输入 URL 到页面显示发生了什么?

标准回答

建议按链路回答:

URL 解析

DNS

建立网络连接

HTTPS / TLS

HTTP 请求

服务器响应 HTML

HTML 解析为 DOM

CSS 解析为 CSSOM

生成 Render Tree

Layout

Paint

Composite

37. 浏览器的渲染流程是什么?

标准回答

重点:

  • HTML -> DOM
  • CSS -> CSSOM
  • DOM + CSSOM -> Render Tree
  • Layout:计算尺寸位置
  • Paint:生成绘制记录
  • Composite:合成图层

38. 重排和重绘有什么区别?

标准回答

重排 / Layout

元素几何信息变化,需要重新计算布局。

例如:

  • width
  • height
  • position
  • font-size
  • DOM 插入删除

重绘 / Paint

视觉变化但布局不一定变。

例如:

  • color
  • background

39. 什么是 Layout Thrashing?

标准回答

连续交替执行:

写样式
读布局
写样式
读布局

可能迫使浏览器频繁同步计算布局。

例如读取:

offsetHeight
getBoundingClientRect()

优化方式:

  • 批量读
  • 批量写
  • 使用 requestAnimationFrame
  • 减少高频 DOM 测量

40. 浏览器缓存有哪些?

标准回答

常见:

  • Memory Cache
  • Disk Cache
  • HTTP Cache
  • Service Worker Cache

HTTP 缓存重点分:

  • 强缓存
  • 协商缓存

41. 强缓存和协商缓存有什么区别?

标准回答

强缓存:

Cache-Control: max-age=...

缓存仍新鲜时直接使用本地副本,不需要向服务端确认。

协商缓存:

ETag
If-None-Match

Last-Modified
If-Modified-Since

客户端会与服务端确认资源是否变化。

42. Cookie、LocalStorage、SessionStorage、IndexedDB 区别?

标准回答

Cookie

  • 会自动随匹配请求发送。
  • 容量较小。
  • 可以设置 HttpOnly / Secure / SameSite。

LocalStorage

  • 同源长期保存。
  • 同步 API。

SessionStorage

  • 生命周期主要跟当前标签页会话相关。

IndexedDB

  • 异步。
  • 适合大量结构化数据和离线场景。

43. XSS 是什么?

标准回答

攻击者将恶意脚本注入页面并执行。

防护:

  • 输出编码
  • 避免直接插入不可信 HTML
  • CSP
  • Cookie 使用 HttpOnly
  • 富文本严格白名单过滤

44. CSRF 是什么?

标准回答

攻击者诱导用户浏览器向已登录站点发送非预期请求。

常见防护:

  • SameSite Cookie
  • CSRF Token
  • 校验 Origin / Referer
  • 敏感操作二次确认

45. HTTP/1.1、HTTP/2、HTTP/3 有什么区别?

标准回答

HTTP/1.1:

  • 文本协议
  • keep-alive
  • 多请求并发能力有限

HTTP/2:

  • 二进制分帧
  • 多路复用
  • Header Compression
  • 一个 TCP 连接承载多个 stream

HTTP/3:

  • 基于 QUIC
  • 运行在 UDP 之上
  • 避免 TCP 层的连接级队头阻塞

46. HTTPS 为什么安全?

标准回答

HTTPS = HTTP + TLS。

TLS 主要解决:

  • 机密性
  • 完整性
  • 身份认证

握手阶段通常借助非对称密码体系完成身份验证和密钥协商,之后使用对称加密传输数据。

47. TCP 为什么是三次握手?

标准回答

核心目标是双方确认:

  • 自己的发送能力
  • 自己的接收能力
  • 对方的发送能力
  • 对方的接收能力

以及同步双方初始序列号。

五、React

48. React 为什么需要 Virtual DOM?

标准回答

不要说“Virtual DOM 一定比真实 DOM 快”。

更准确的说法:

Virtual DOM 为声明式 UI 提供中间表示,通过 reconciliation 比较新旧树,计算必要更新,再统一提交到真实 DOM。

价值:

  • 声明式编程
  • 跨平台抽象
  • 可预测更新
  • 为调度和并发能力提供基础

49. React Diff 的基本思想是什么?

标准回答

经典 React reconciliation 依赖几个重要假设:

  • 不同类型元素通常产生不同子树。
  • 同层级进行比较。
  • 使用 key 帮助识别列表节点。

这样把通用树比较问题降到接近 O(n)。

50. 为什么不建议用数组 index 作为 key?

标准回答

如果列表存在:

  • 插入
  • 删除
  • 排序

index 会发生变化,React 可能错误复用组件实例,造成:

  • 状态错位
  • 输入框值错位
  • 不必要更新

稳定业务 id 更合适。

51. React Fiber 是什么?

标准回答

Fiber 可以理解为 React 内部对组件工作单元的结构化表示。

价值:

  • 把渲染工作拆成可管理单元。
  • 支持暂停、恢复、优先级调度。
  • 为并发渲染提供基础。

52. React 渲染阶段和提交阶段有什么区别?

标准回答

Render Phase:

  • 计算新 UI。
  • 可以被打断、重做。
  • 不应该产生不可逆副作用。

Commit Phase:

  • 真正修改 DOM。
  • 执行 layout effect 等提交相关逻辑。
  • 不可随意中断。

53. useEffectuseLayoutEffect 区别?

标准回答

useLayoutEffect

  • DOM 提交后、浏览器绘制前同步执行。
  • 适合测量布局、立即修正视觉状态。

useEffect

  • 更晚执行。
  • 不阻塞浏览器绘制。
  • 更适合大多数副作用。

54. 什么是 stale closure?

标准回答

函数捕获的是创建时的变量环境。

const [count, setCount] = useState(0)

useEffect(() => {
  const timer = setInterval(() => {
    console.log(count)
  }, 1000)

  return () => clearInterval(timer)
}, [])

这里 interval 回调捕获的是首次 render 的 count

解决思路:

  • 正确依赖
  • 函数式更新
  • ref
  • 调整逻辑结构

55. useMemouseCallback 什么时候用?

标准回答

useMemo 缓存计算结果。

useCallback 缓存函数引用。

适合:

  • 计算成本明显高
  • 需要保持引用稳定
  • 配合 memoized child
  • 依赖引用稳定的第三方 API

不应该机械地“全部包一层”,memo 本身也有比较和缓存成本。

56. React 18 的 Automatic Batching 是什么?

标准回答

React 会尽量把多个状态更新合并到一次 render。

这样可以减少重复渲染。

57. startTransition / useTransition 是干什么的?

标准回答

用于把不紧急更新标记为 transition,让输入等紧急交互保持更高响应优先级。

典型:

输入框值更新紧急
大列表筛选结果可以 transition

58. Suspense 的核心思想是什么?

标准回答

让组件树能够声明“某部分暂时还不能完成”,然后由上层 Suspense boundary 展示 fallback。

它可以配合:

  • lazy
  • 服务端流式渲染
  • 支持 Suspense 的数据框架

59. Server Component 和 Client Component 的区别?

标准回答

Server Component:

  • 在服务端执行。
  • 可以直接访问服务端资源。
  • 不会把组件本身对应的客户端 JS 全部发到浏览器。
  • 不能使用依赖浏览器运行环境的交互 Hook,例如 useEffect

Client Component:

  • 在浏览器参与交互。
  • 可以使用 state、effect、事件处理。

结合你的简历怎么答

你在 GamePortal 中使用了 Next.js App Router,所以面试时可以强调:

页面并不是全部一股脑做成客户端组件。对于需要交互和实时状态的区域使用 Client Component,而偏展示、首屏和服务端数据获取的部分可以尽可能留在服务端边界,从而减少客户端 JS。

60. React 状态更新为什么不是“修改变量”?

标准回答

React state 更像某次 render 的快照。

调用 setState 是“请求一次新的 render”,而不是直接改变当前函数调用里的变量。

六、Vue / Nuxt

61. Vue 2 和 Vue 3 响应式有什么区别?

标准回答

Vue 2 主要基于 Object.defineProperty

Vue 3 主要基于 Proxy

Proxy 优势:

  • 可以拦截更多操作。
  • 对新增 / 删除属性处理自然。
  • 对数组和对象能力更完整。

62. refreactive 有什么区别?

标准回答

ref

  • 可以包装 primitive。
  • 通过 .value 访问。

reactive

  • 主要包装对象。
  • 返回 Proxy。

实际项目更关注语义和可维护性,而不是绝对二选一。

63. computedwatchwatchEffect 区别?

标准回答

computed

  • 声明派生状态。
  • 有缓存语义。
  • 适合根据已有响应式状态得到新值。

watch

  • 明确监听指定源。
  • 能访问新旧值。

watchEffect

  • 自动收集执行过程中使用的响应式依赖。
  • 更偏副作用。

64. nextTick 是干什么的?

标准回答

Vue 会批量调度响应式更新,不一定同步立刻更新 DOM。

nextTick 用于等待本轮 DOM 更新提交完成。

65. Vue 组件为什么也需要 key?

标准回答

key 用于帮助 diff 算法识别节点身份。

稳定 key 能正确复用或重建节点。

66. Vue 的 Composition API 有什么优势?

标准回答

主要不是“更高级”,而是:

  • 逻辑可以按功能组织。
  • 更容易提取 composable。
  • TypeScript 体验更自然。
  • 复杂组件中相关逻辑不必被 data/methods/watch 分散。

67. Nuxt 的 SSR 解决什么问题?

标准回答

主要价值:

  • 首屏 HTML
  • SEO
  • 更好的初始内容可见性

同时要付出:

  • 服务端运行成本
  • 数据序列化
  • hydration 一致性
  • 服务端 / 客户端环境差异处理

结合你的简历怎么答

你在跨境业务项目中用 Nuxt 3 沉淀过独立站模板,并包含 SEO、GTM、多语言和多币种。

可以回答:

独立站类页面本身就比较看重 SEO 和首屏,所以官网和营销页面优先使用 SSR/SSG 思路,而任务型后台更偏 SPA。

68. React 和 Vue 的响应式理念有什么本质区别?

标准回答

React 更偏:

state 改变 -> 重新执行组件函数 -> 重新计算 UI

Vue 更偏:

Proxy 收集依赖 -> 精确触发依赖更新

两者都可以做高性能 UI,只是响应式模型不同。

七、TanStack Query / 状态管理

69. TanStack Query 和 Zustand / Pinia 的区别?

标准回答

TanStack Query 更偏 Server State

  • 异步
  • 来自服务端
  • 可能失效
  • 要刷新
  • 要重试
  • 要缓存
  • 要同步

Zustand / Pinia 更偏 Client State

  • UI 状态
  • 本地业务状态
  • 跨组件共享状态

结合你的简历怎么答

你的项目里同时使用过:

  • TanStack Query
  • Zustand
  • Jotai
  • Pinia

建议表达:

我会优先区分 server state 和 client state。比如游戏列表、任务、用户、数据集这类接口数据交给 Query;当前选中项、弹窗、本地交互或跨组件临时状态才会放 Zustand / Jotai / Pinia。

70. staleTime 是什么?

标准回答

staleTime 控制的是:

数据多长时间内被认为是 fresh。

它不等同于:

过了这个时间数据就一定马上重新请求。

是否重新请求,还受:

  • 组件挂载
  • refetchOnWindowFocus
  • refetchOnReconnect
  • 手动 invalidate
  • 其他策略

影响。

71. gcTime 是什么?

标准回答

当 Query 没有活跃 observer 后,会进入 inactive 状态。

gcTime 决定 inactive query 多久后从缓存中被回收。

72. Query Key 为什么重要?

标准回答

Query Key 是服务端数据缓存身份。

例如:

['games', category, page]

不同参数应该构成不同 key。

如果 key 设计错误,会出现:

  • 错误复用缓存
  • 数据串页
  • invalidate 范围不准确

73. invalidateQueriessetQueryData 的区别?

标准回答

invalidateQueries

  • 标记数据失效。
  • 通常触发后续重新获取。

setQueryData

  • 直接更新本地缓存。
  • 适合已有新数据,例如 WebSocket 推送或 mutation 乐观更新。

结合你的简历怎么答

GamePortal 里的任务状态更新可以这样讲:

WebSocket 已经把最新任务状态主动推送到了客户端,所以没必要收到事件后再发一次 HTTP。直接通过 queryClient.setQueryData 更新对应 Query Cache,可以让所有消费该 query 的组件同步刷新。

74. 为什么 WebSocket 数据不全部放 Zustand?

标准回答

如果数据本质仍然是服务端资源:

  • 有 API 初始加载
  • 有刷新
  • 有缓存
  • 有失效
  • 有服务端来源

那继续保留在 TanStack Query 更自然。

WebSocket 只是改变“数据更新通道”,不改变“数据属于 Server State”这个事实。

结合你的简历怎么答

你的项目正好是:

初始数据HTTP Query
实时变化WebSocket
最终状态Query Cache

这套回答很贴合实际经历。

75. 乐观更新怎么做?

标准回答

流程一般是:

  1. 暂停相关查询。
  2. 保存旧缓存。
  3. 立即更新本地缓存。
  4. mutation 失败则 rollback。
  5. 最后重新 invalidate 保证最终一致。

八、Next.js / SSR / 性能优化

76. CSR、SSR、SSG 的区别?

标准回答

CSR

浏览器获取 JS 后生成页面。

优点:

  • 服务端压力低。
  • 后台系统开发模型简单。

缺点:

  • 首屏依赖 JS。
  • SEO 相对不利。

SSR

每次请求在服务端生成 HTML。

优点:

  • 内容首屏可见。
  • SEO 友好。
  • 数据实时性高。

缺点:

  • 服务端计算成本。
  • TTFB 可能增加。

SSG

构建阶段提前生成 HTML。

优点:

  • CDN 友好。
  • 性能非常稳定。

缺点:

  • 内容实时性不如 SSR。

结合你的简历怎么答

你简历明确写了 CSR / SSR / SSG 方案设计,并且:

  • GamePortal:Next.js
  • 跨境独立站:Nuxt
  • 博客:Qwik + SSG / SSR 混合

可以结合业务类型说明“不是固定选一种”。

77. 什么是 Hydration?

标准回答

SSR 返回 HTML 后,浏览器还需要加载客户端 JS,让这些已有 DOM 重新具备:

  • 事件
  • 状态
  • 交互能力

这个过程叫 hydration。

78. 什么会导致 Hydration Mismatch?

标准回答

服务端和客户端第一次渲染结果不一致。

典型:

<div>{Date.now()}</div>

或者:

  • 随机数
  • 依赖 window
  • 浏览器 locale
  • 客户端才有的数据
  • 条件渲染不同

79. Next.js App Router 的核心价值有哪些?

标准回答

可以重点说:

  • Layout
  • Server Component
  • 嵌套路由
  • 流式渲染
  • Suspense 边界
  • 服务端数据获取模型

结合你的简历怎么答

你 GamePortal 是基于 App Router 的大型业务页面,面试时不要只背概念,要把它落到:

  • 游戏大厅
  • 账户
  • 支付
  • KYC
  • 活动

这些不同模块的页面边界和数据获取方式上。

80. 什么是 Core Web Vitals?

标准回答

重点准备:

  • LCP:最大内容绘制
  • INP:交互响应
  • CLS:布局偏移

同时可补充:

  • FCP
  • TTFB

81. IntersectionObserver 为什么比直接监听 scroll 更适合懒加载?

标准回答

scroll 事件可能非常高频,开发者需要自己:

  • 计算位置
  • 节流
  • 判断 viewport

IntersectionObserver 由浏览器统一观察元素与 viewport 的相交状态,更适合:

  • 懒加载
  • 无限列表
  • 预加载
  • 曝光统计

结合你的简历怎么答

你在 GamePortal 首页游戏列表中用它做“接近列表底部时预加载”。

可以说:

我的目标不是等用户滚到底才请求,而是提前一定阈值开始拉下一页,让分页对用户感知更弱。

82. 图片优化有哪些?

标准回答

  • 响应式图片
  • srcset / sizes
  • WebP / AVIF
  • 合理尺寸
  • lazy loading
  • preload 首屏关键图
  • CDN
  • 合理 quality
  • 避免布局抖动
  • 根据设备像素密度选择资源

结合你的简历怎么答

你简历中写了基于 next/image 封装响应式图片组件:

  • 加载失败兜底
  • 质量映射
  • 设备尺寸自适应
  • 13 个图片宽度断点
  • 20 档质量白名单

面试官如果问“为什么质量不直接 100”,可以答:

图片质量提升并不是线性收益,但文件体积可能明显增加,因此要根据资源类型、展示尺寸和 DPR 做权衡。

83. 首屏性能优化有哪些思路?

标准回答

可以从四层讲:

网络

  • CDN
  • HTTP 缓存
  • 压缩
  • preload

资源

  • code splitting
  • tree shaking
  • 图片优化
  • 字体优化

渲染

  • SSR / SSG
  • 减少阻塞
  • 合理 Suspense

数据

  • 减少首屏接口量
  • 分页
  • 缓存
  • 并行请求
  • 预取

结合你的简历怎么答

你有两个很好的真实案例:

  1. 独立站:组件懒加载、图片压缩、无用资源清理。
  2. GamePortal:分页降低首屏数据量 + 5 分钟缓存新鲜期 + IntersectionObserver 预加载。

84. 怎么做性能优化才不会变成“玄学”?

标准回答

要有:

指标 -> 定位 -> 改动 -> 对比 -> 回归

常用:

  • Performance
  • Lighthouse
  • Web Vitals
  • bundle analyzer
  • 网络瀑布图
  • React Profiler

不要只说“我用了 memo,所以性能变好了”。

九、WebSocket / IM / 实时系统

85. WebSocket 和 HTTP 有什么区别?

标准回答

HTTP:

  • 请求 / 响应。
  • 适合资源请求、API。

WebSocket:

  • 建立持久双向连接。
  • 适合服务器主动推送。

典型:

  • IM
  • 实时通知
  • 游戏状态
  • 行情

86. WebSocket 是怎么建立连接的?

标准回答

最初通常从 HTTP Upgrade 开始:

Connection: Upgrade
Upgrade: websocket

握手成功后切换到 WebSocket 协议。

87. WebSocket 断线重连怎么设计?

标准回答

常见:

  • 指数退避
  • jitter
  • 最大间隔
  • 网络恢复后立即重连
  • 页面可见性恢复
  • 登录态变化重新建立连接

避免所有客户端固定 1 秒重连,否则服务恢复瞬间容易产生“惊群”。

88. WebSocket 重连后怎么补数据?

标准回答

实时连接不能天然保证“断线期间消息一定不会漏”。

常见方案:

维护 lastSequenceId

重连成功

向服务端补拉 lastSequenceId 之后的数据

恢复实时流

也可以配合:

  • 时间戳
  • event id
  • version
  • cursor

结合你的简历怎么答

你的 GamePortal 已经有:

WebSocket 主链路 + 增量拉取 + 10 分钟低频兜底

这是非常好的“实时 + 最终一致”案例。

89. 消息重复怎么办?

标准回答

客户端和服务端都应该考虑幂等。

常见:

  • messageId
  • eventId
  • requestId
  • sequenceId

客户端维护已处理 id 或服务端做幂等写入。

90. IM 消息怎么保证顺序?

标准回答

网络到达顺序不等于业务顺序。

常见方案:

  • 服务端生成单调递增 sequence
  • 客户端按 sequence 排序
  • 检测 gap
  • 缺失区间补拉

结合你的简历怎么答

你做过:

  • 实时消息
  • 在线状态
  • 未读计数
  • 会话转接
  • 草稿
  • 消息已读
  • 消息转发

这些都可以围绕“会话维度状态”和“消息顺序”展开。

91. 未读消息数怎么设计?

标准回答

一种常见思路:

latestMessageSeq
lastReadSeq

通过两者关系得到未读范围。

实际系统还要考虑:

  • 被撤回消息
  • 系统消息
  • 免打扰
  • 跨设备已读同步
  • 群聊

十、工程化 / Electron / Node.js

92. Vite 为什么开发环境启动快?

标准回答

传统 bundle-based dev server 往往先把模块打包。

Vite 开发环境主要利用浏览器原生 ESM:

按需请求模块

依赖预构建等环节使用高性能工具,因此大项目启动体验通常更快。

93. Tree Shaking 原理是什么?

标准回答

依赖 ES Module 的静态结构:

import { foo } from './module'

构建工具可以在构建阶段分析:

  • 哪些 export 被使用
  • 哪些代码不可达

从而删除无用代码。

94. 为什么 CommonJS 不利于 Tree Shaking?

标准回答

CommonJS 依赖可以动态执行:

const module = require(name)

依赖关系更偏运行时,因此静态分析难度更高。

95. pnpm 的特点是什么?

标准回答

重点:

  • content-addressable store
  • 硬链接
  • 符号链接
  • workspace
  • 节省磁盘
  • 依赖结构更严格

追问:什么是幽灵依赖?

项目直接 import 了一个没有声明在自己 package.json 中的包,只是因为它被其他依赖“顺便”安装到可访问位置。

pnpm 的结构能减少这种问题。

96. Electron 的 Main Process 和 Renderer Process 区别?

标准回答

Main:

  • 管理窗口
  • 生命周期
  • 原生能力
  • Node 能力

Renderer:

  • 负责 UI
  • 更像浏览器页面

中间通过 IPC 通信。

结合你的简历怎么答

你经历过:

NW.js -> Electron

并做过 Windows / macOS 打包,所以除了概念,还可以讲迁移和发布流程。

97. 为什么 Electron 不建议直接打开 nodeIntegration

标准回答

如果 renderer 中执行了不可信内容,同时拥有 Node 权限,风险会非常高。

更推荐:

  • contextIsolation
  • preload
  • contextBridge
  • 暴露最小能力 API

十一、AI / Agent / SSE

98. RAG 的完整链路是什么?

标准回答

用户问题

生成 / 改写检索 query

Retrieval

Relevant Chunks

构造 Context

LLM 生成

返回答案 + 来源

结合你的简历怎么答

你的 Joplin Agent:

  • 按 Markdown 标题结构切片
  • SQLite FTS5 + BM25
  • 只把命中片段提交给模型
  • 返回可追溯来源

这些都可以直接作为回答。

99. Tool Calling Agent 循环是什么?

标准回答

一个典型流程:

User

LLM

tool_calls

执行工具

Tool Result

再次交给 LLM

继续调用工具 / 输出最终答案

关键设计:

  • 最大步数
  • 工具 schema
  • 超时
  • 错误处理
  • 上下文控制
  • 防止无限循环

结合你的简历怎么答

你的实现中已经有:

  • 有限步数 Agent 循环
  • search_documents
  • 多轮检索
  • tool_calls 解析

所以这题属于非常强的项目题。

100. SSE 和 WebSocket 应该怎么选?

标准回答

SSE

特点:

  • 基于 HTTP
  • 服务端 -> 客户端单向流
  • 文本事件模型
  • 浏览器原生 EventSource 有自动重连能力

适合:

  • LLM Token Streaming
  • 服务端事件流
  • 日志流
  • 进度流

WebSocket

特点:

  • 全双工
  • 客户端和服务端都可以主动发送
  • 更适合实时交互

适合:

  • IM
  • 在线状态
  • 游戏实时数据
  • 协作系统

结合你的简历怎么答

你刚好两种都用了:

GamePortal / IM
WebSocket

Joplin Agent 流式回答
SSE

这是非常理想的面试回答案例:

我不是按“哪个更新”来选,而是根据通信模型选。业务实时状态有明显双向交互、在线状态和重连需求,所以用 WebSocket;LLM 回答主要是服务端持续向前端输出进度、检索、回答和来源事件,单向流式传输就够了,所以 SSE 更简单。

十二、基于你简历的重点项目追问题

下面这些题,建议比普通八股优先级更高。

A. GamePortal

1. 为什么把 15 秒轮询改成 WebSocket?

回答结构:

原问题
HTTP 周期轮询量大
数据其实具有事件驱动特征
改用 WebSocket
推送结果写入 Query Cache
UI 自动同步

你简历里的数据:

单用户每小时约 240 次周期请求
0

要准备回答:

  • WebSocket 断了怎么办?
  • 为什么不保留 15 秒兜底?
  • 怎么保证任务状态最终一致?
  • 多 tab 是否会建立多个连接?
  • 消息乱序怎么办?

2. 走马灯为什么不是纯 WebSocket?

你的方案:

WebSocket 主链路
+
增量拉取
+
并发请求复用
+
10 分钟低频兜底

这是一个非常好的回答模板:

实时链路负责低延迟,HTTP 低频兜底负责最终一致,不把系统可靠性完全押在长连接上。

3. TanStack Query 的缓存策略怎么定?

你简历里有:

游戏列表 5 分钟 staleTime

面试时准备说明:

  • 为什么是 5 分钟?
  • 游戏列表和任务状态 freshness 是否一样?
  • 哪些 query 可以长缓存?
  • 哪些必须实时?

核心答案:

缓存策略应该按数据变化频率和用户对实时性的敏感度来定,而不是统一一个时间。

B. 跨境独立站 / Nuxt

4. 34 个站点怎么避免复制粘贴?

你简历里是:

  • 26 个招聘站
  • 8 个商城站
  • Nuxt 3 模板
  • 多语言
  • 多币种
  • 市场差异化
  • GTM
  • SEO

可以围绕:

共享组件
+
主题 / 配置
+
站点级配置
+
运行时 / 构建时变量

说明平台化思路。

C. 在线教育平台

5. React 和 Vue 项目为什么都引入 TanStack Query?

回答重点:

不因为框架不同就维护两套 server state 思想。服务端数据获取、缓存、失效、重试这些问题是通用的,所以 React/Vue 都统一使用 Query 模型。

6. Android WebView 适配有什么坑?

准备:

  • JSBridge
  • 生命周期
  • 音频状态
  • 页面返回
  • 原生跳转
  • Android 低版本浏览器能力
  • viewport
  • file / media 权限
  • 页面缓存

你简历里有实际的:

  • 音频控制
  • 学习回调
  • 原生跳转

D. 企业 IM

7. 会话转接为什么复杂?

需要考虑:

  • 当前客服
  • 目标客服 / 组
  • 会话归属
  • 历史消息
  • 未读
  • 在线状态
  • 转接事件
  • Web 和 Electron 一致性

8. 已读状态怎么同步?

准备:

用户读取

客户端提交 lastReadSeq

服务端持久化

同步到其他客户端

计算 unread

E. Joplin Agent

9. 为什么用 FTS5 + BM25,而不是向量数据库?

你简历只写了:

  • SQLite FTS5
  • BM25
  • 本地知识库

因此面试可以保守回答:

这是本地工具,优先考虑部署简单、离线、轻量;FTS5 已经能很好支持关键词检索。是否引入 embedding / vector search,要看语义召回不足是否真的成为瓶颈。

不要虚构你已经做了向量检索。

10. 为什么只把命中片段发给模型?

因为:

  • 控制 token
  • 降低无关上下文
  • 提高可追溯性
  • 降低模型被大量无关笔记干扰的概率

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
八股文2026-08-27
定时器按顺序播放多个音频,切后台音频会乱
本身不会补触发,但 它会被系统级冻结 ——iOS Safari 后台完全停止计时,切回前台后 只补执行一次 (不是不补,而是"缺失的中间状态补不上")。间隔短的不会出大问题, 间隔长的会出现"音频流被压缩" ——切回来后本该播 5 分钟的间隙,实际只过了 3 分钟,结果整段对不上。 核心原则 :定时器只能用来"提醒一次", 真正决定"该不该播"的是绝对时间戳 。 setTimeout 在后台会发生什么(按平台) | 平台 | 后台行为...
面试
八股文2026-08-27
线上项目白屏的原因
白屏的本质是 渲染管线某一环断了 ——可能是资源、JS、接口、路由、样式、兼容性任一环节出问题。排查按"控制台 → 网络 → DOM → 环境"四步定位。 本质(前端类比) 把网页想成一栋楼: 白屏 ≠ 一定是同一种原因——这是面试想听的层次。 六大类原因(按出现频率) | 类别 | 典型表现 | 真实案例 | | : | : | : | | ① 资源加载失败 | DOM 是空的 | 入口 JS 404、CDN 挂了、CSS 阻塞 |...
面试
八股文2026-08-27
背景图就是 1MB 大图,怎么优化
1MB 大背景图优化分 三步 :压缩体积(10 30x)、按需加载(按设备/视口/网速)、渲染期优化(GPU 合成)。背景图跟 不同——它是 CSS,不走浏览器的原生懒加载机制,得手动处理。 背景图 vs 的关键区别(先讲清楚这个) 这是面试官想听的"针对性认知"——背景图不是普通图片,不能套通用方案。 三步优化(按优先级) 第 1 步:压缩体积(最重要,立竿见影) 1MB 的来源一般是这几种 ,对应解决方案: | 原始问题 | 体积来...
面试
八股文2026-08-27
项目里很多图片和视频,怎么优化
图片视频优化分 四层 :网络层(CDN/格式)、加载层(懒加载/预加载)、渲染层(解码/缓存)、业务层(按需/降级)。面试要把这四层都讲到位才算有体系。 四层优化模型 第 1 层:网络层(省钱、省时间) 核心目标:让资源体积小、让用户拿到资源快 | 手段 | 作用 | 关键点 | | : | : | : | | 图片格式 | WebP/AVIF 比 JPEG 小 25 50% | 兼容 fallback | | 视频格式 | H.265...
面试
八股文2026-08-20
Embedding 向量模型:从语义表示到相似度计算
前言 大模型「读懂」文字靠的是 token,但 token 之间只有离散的编号关系,模型并不知道「苹果」和「李子」在语义上很近。要让程序能「理解」两段文字的相似程度,必须先把文本映射成一个 高维向量 ,再用几何方法比较。这一步就是 Embedding。 本篇基于我本地 今天的真实代码,从语义表示讲到余弦相似度,并复盘几个踩过的真实坑。 一、为什么需要 Embedding 传统关键词检索是「字面匹配」: Embedding 做的是「语义匹...
面试
八股文2026-08-19
深入理解 JavaScript 原型与原型链:从关系图到核心逻辑 - heshanwan - 博客园
在 JavaScript 世界里, 原型(Prototype)和原型链(Prototype Chain) 是理解对象继承、属性查找机制的基石。很多开发者初学时对它们 “又爱又恨”,这篇文章将结合经典关系图,用通俗易懂的方式拆解原型与原型链的核心逻辑,帮你彻底掌握这套机制! &nbsp; 一、先搞懂几个核心概念 在分析关系图前,先明确 JavaScript 中与原型相关的关键概念,避免后续混淆: 1\. 函数对象与普通对象 函数对象 :由...
面试

评论

请登录后发表评论

去登录
加载评论中...

目录