HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 技术面试完全指南

    • 技术面试完全指南
    • 8年面试官告诉你:90%的简历在第一轮就被刷掉了
    • 刷了500道LeetCode,终于明白大厂算法面试到底考什么
    • 高频算法题精讲-双指针与滑动窗口
    • 03-高频算法题精讲-二分查找与排序
    • 04-高频算法题精讲-树与递归
    • 05-高频算法题精讲-图与拓扑排序
    • 06-高频算法题精讲-动态规划
    • Go面试必问:一道GMP问题,干掉90%的候选人
    • 08-数据库面试高频题
    • 09-分布式系统面试题
    • 10-Kubernetes与云原生面试题
    • 11-系统设计面试方法论
    • 前端面试高频题
    • AI 与机器学习面试题
    • 行为面试与软技能

前端面试高频题

一、Vue 核心原理

1. Vue 3 响应式系统原理

问题:Vue 3 的响应式系统相比 Vue 2 有哪些改进?请详细说明 Proxy 的优势。

回答要点:

Vue 3 使用 Proxy 替代了 Vue 2 的 Object.defineProperty,带来以下核心改进:

  1. 完整的对象监听能力

    • Proxy 可以拦截对象的所有操作(13种),而 Object.defineProperty 只能监听属性的读写
    • 可以监听数组索引和 length 属性的变化
    • 可以监听动态添加的属性
  2. 性能优化

    • 懒监听:只有被访问的属性才会被追踪
    • 不需要递归遍历所有属性进行劫持
    • 初始化性能更好

代码示例:

// Vue 3 响应式实现核心
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      // 依赖收集
      track(target, key)
      // 深层响应式
      if (typeof result === 'object' && result !== null) {
        return reactive(result)
      }
      return result
    },
    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 && result) {
        trigger(target, key)
      }
      return result
    }
  })
}

// 依赖收集
let activeEffect = null
const targetMap = new WeakMap()

function track(target, key) {
  if (!activeEffect) return

  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }

  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }

  dep.add(activeEffect)
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

// 副作用函数
function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

2. Vue 3 Composition API 设计思想

问题:Composition API 解决了什么问题?它的组合逻辑如何优于 Options API?

核心优势:

  1. 逻辑复用更优雅

    • 替代 mixins,避免命名冲突
    • 清晰的数据来源
    • 更好的类型推导
  2. 代码组织更灵活

    • 按功能组织代码,而非按选项类型
    • 相关逻辑集中管理
    • 更好的可读性和维护性

实战示例:

// 可复用的组合函数
import { ref, onMounted, onUnmounted } from 'vue'

// 鼠标位置追踪
function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove', update)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })

  return { x, y }
}

// 数据请求
function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  async function fetchData() {
    loading.value = true
    error.value = null

    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  onMounted(() => {
    fetchData()
  })

  return { data, error, loading, refetch: fetchData }
}

// 组件中使用
export default {
  setup() {
    const { x, y } = useMouse()
    const { data, error, loading } = useFetch('/api/users')

    return {
      x,
      y,
      data,
      error,
      loading
    }
  }
}

3. Vue 虚拟 DOM 和 Diff 算法

问题:详细描述 Vue 3 的 Diff 算法优化策略。

核心优化:

  1. 编译时优化

    • 静态提升(Static Hoisting)
    • 补丁标记(Patch Flags)
    • 树结构打平(Block Tree)
  2. 运行时优化

    • 最长递增子序列算法(LIS)
    • 快速路径判断

Diff 算法实现:

function patchChildren(n1, n2, container) {
  const c1 = n1.children
  const c2 = n2.children

  // 快速路径:文本节点
  if (typeof c2 === 'string') {
    if (c1 !== c2) {
      container.textContent = c2
    }
    return
  }

  // 数组 Diff
  const oldLength = c1.length
  const newLength = c2.length
  const commonLength = Math.min(oldLength, newLength)

  // 1. 从头部开始对比
  let i = 0
  while (i < commonLength && c1[i].key === c2[i].key) {
    patch(c1[i], c2[i], container)
    i++
  }

  // 2. 从尾部开始对比
  let e1 = oldLength - 1
  let e2 = newLength - 1
  while (e1 >= i && e2 >= i && c1[e1].key === c2[e2].key) {
    patch(c1[e1], c2[e2], container)
    e1--
    e2--
  }

  // 3. 只有新增节点
  if (i > e1 && i <= e2) {
    while (i <= e2) {
      mount(c2[i], container)
      i++
    }
  }
  // 4. 只有删除节点
  else if (i > e2 && i <= e1) {
    while (i <= e1) {
      unmount(c1[i])
      i++
    }
  }
  // 5. 乱序情况:最长递增子序列
  else {
    const s1 = i
    const s2 = i

    // 构建新节点的 key -> index 映射
    const keyToNewIndexMap = new Map()
    for (i = s2; i <= e2; i++) {
      keyToNewIndexMap.set(c2[i].key, i)
    }

    // 用于存储新节点在旧节点中的位置
    const newIndexToOldIndexMap = new Array(e2 - s2 + 1).fill(-1)

    let patched = 0
    const toBePatched = e2 - s2 + 1
    let moved = false
    let maxNewIndexSoFar = 0

    // 遍历旧节点
    for (i = s1; i <= e1; i++) {
      const prevChild = c1[i]

      if (patched >= toBePatched) {
        // 所有新节点都已处理,删除剩余旧节点
        unmount(prevChild)
        continue
      }

      const newIndex = keyToNewIndexMap.get(prevChild.key)

      if (newIndex === undefined) {
        // 旧节点在新节点中不存在,删除
        unmount(prevChild)
      } else {
        // 记录位置关系
        newIndexToOldIndexMap[newIndex - s2] = i

        // 判断是否需要移动
        if (newIndex >= maxNewIndexSoFar) {
          maxNewIndexSoFar = newIndex
        } else {
          moved = true
        }

        // 更新节点
        patch(prevChild, c2[newIndex], container)
        patched++
      }
    }

    // 计算最长递增子序列
    const increasingNewIndexSequence = moved
      ? getSequence(newIndexToOldIndexMap)
      : []

    let j = increasingNewIndexSequence.length - 1

    // 倒序遍历,插入和移动节点
    for (i = toBePatched - 1; i >= 0; i--) {
      const nextIndex = s2 + i
      const nextChild = c2[nextIndex]
      const anchor = nextIndex + 1 < newLength ? c2[nextIndex + 1].el : null

      if (newIndexToOldIndexMap[i] === -1) {
        // 新增节点
        mount(nextChild, container, anchor)
      } else if (moved) {
        // 需要移动
        if (j < 0 || i !== increasingNewIndexSequence[j]) {
          move(nextChild, container, anchor)
        } else {
          j--
        }
      }
    }
  }
}

// 最长递增子序列算法
function getSequence(arr) {
  const len = arr.length
  const result = [0]
  const p = arr.slice()

  let i, j, u, v, c

  for (i = 0; i < len; i++) {
    const arrI = arr[i]
    if (arrI !== -1) {
      j = result[result.length - 1]
      if (arr[j] < arrI) {
        p[i] = j
        result.push(i)
        continue
      }

      u = 0
      v = result.length - 1

      while (u < v) {
        c = ((u + v) / 2) | 0
        if (arr[result[c]] < arrI) {
          u = c + 1
        } else {
          v = c
        }
      }

      if (arrI < arr[result[u]]) {
        if (u > 0) {
          p[i] = result[u - 1]
        }
        result[u] = i
      }
    }
  }

  u = result.length
  v = result[u - 1]

  while (u-- > 0) {
    result[u] = v
    v = p[v]
  }

  return result
}

二、React 核心原理

4. React 18 并发特性

问题:React 18 的并发渲染(Concurrent Rendering)是如何实现的?

核心概念:

  1. 时间切片(Time Slicing)

    • 将长任务拆分成多个小任务
    • 每个任务执行 5ms 后交还控制权
    • 使用 MessageChannel 实现调度
  2. 优先级调度

    • 不同更新具有不同优先级
    • 高优先级任务可以打断低优先级任务
    • 过期任务会被提升优先级

实现原理:

// Scheduler 核心实现
const ImmediatePriority = 1
const UserBlockingPriority = 2
const NormalPriority = 3
const LowPriority = 4
const IdlePriority = 5

// 任务队列
let taskQueue = []
let timerQueue = []

// 当前任务
let currentTask = null
let isHostCallbackScheduled = false
let isPerformingWork = false

// 调度任务
function scheduleCallback(priorityLevel, callback, options) {
  const currentTime = getCurrentTime()

  let startTime
  if (options && typeof options.delay === 'number') {
    startTime = currentTime + options.delay
  } else {
    startTime = currentTime
  }

  let timeout
  switch (priorityLevel) {
    case ImmediatePriority:
      timeout = -1
      break
    case UserBlockingPriority:
      timeout = 250
      break
    case IdlePriority:
      timeout = 1073741823
      break
    case LowPriority:
      timeout = 10000
      break
    case NormalPriority:
    default:
      timeout = 5000
      break
  }

  const expirationTime = startTime + timeout

  const newTask = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    sortIndex: -1
  }

  if (startTime > currentTime) {
    // 延迟任务
    newTask.sortIndex = startTime
    push(timerQueue, newTask)

    if (!isHostCallbackScheduled && peek(taskQueue) === null) {
      requestHostTimeout(handleTimeout, startTime - currentTime)
    }
  } else {
    // 立即执行任务
    newTask.sortIndex = expirationTime
    push(taskQueue, newTask)

    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true
      requestHostCallback(flushWork)
    }
  }

  return newTask
}

// 执行任务
function flushWork(hasTimeRemaining, initialTime) {
  isHostCallbackScheduled = false
  isPerformingWork = true

  try {
    return workLoop(hasTimeRemaining, initialTime)
  } finally {
    currentTask = null
    isPerformingWork = false
  }
}

// 工作循环
function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime

  // 检查延迟任务
  advanceTimers(currentTime)

  currentTask = peek(taskQueue)

  while (currentTask !== null) {
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      // 时间片用完,让出控制权
      break
    }

    const callback = currentTask.callback
    if (typeof callback === 'function') {
      currentTask.callback = null
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime

      const continuationCallback = callback(didUserCallbackTimeout)
      currentTime = getCurrentTime()

      if (typeof continuationCallback === 'function') {
        // 任务未完成,继续执行
        currentTask.callback = continuationCallback
      } else {
        // 任务完成,移除
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue)
        }
      }

      advanceTimers(currentTime)
    } else {
      pop(taskQueue)
    }

    currentTask = peek(taskQueue)
  }

  // 判断是否还有任务
  if (currentTask !== null) {
    return true
  } else {
    const firstTimer = peek(timerQueue)
    if (firstTimer !== null) {
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime)
    }
    return false
  }
}

// 使用 MessageChannel 实现调度
const channel = new MessageChannel()
const port = channel.port2

channel.port1.onmessage = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime()
    const hasTimeRemaining = frameDeadline > currentTime

    try {
      const hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime)

      if (!hasMoreWork) {
        scheduledHostCallback = null
      } else {
        // 还有工作,继续调度
        port.postMessage(null)
      }
    } catch (error) {
      // 发生错误,重新调度
      port.postMessage(null)
      throw error
    }
  }
}

function requestHostCallback(callback) {
  scheduledHostCallback = callback
  port.postMessage(null)
}

// 判断是否应该让出控制权
function shouldYieldToHost() {
  const currentTime = getCurrentTime()
  return currentTime >= frameDeadline
}

5. React Fiber 架构

问题:详细说明 Fiber 架构的工作原理和双缓存机制。

Fiber 节点结构:

function FiberNode(tag, pendingProps, key, mode) {
  // 实例属性
  this.tag = tag
  this.key = key
  this.elementType = null
  this.type = null
  this.stateNode = null

  // Fiber 链表结构
  this.return = null      // 父节点
  this.child = null       // 第一个子节点
  this.sibling = null     // 下一个兄弟节点
  this.index = 0

  this.ref = null

  // 工作单元属性
  this.pendingProps = pendingProps
  this.memoizedProps = null
  this.updateQueue = null
  this.memoizedState = null
  this.dependencies = null

  this.mode = mode

  // Effects
  this.flags = NoFlags
  this.subtreeFlags = NoFlags
  this.deletions = null

  // 优先级
  this.lanes = NoLanes
  this.childLanes = NoLanes

  // 双缓存
  this.alternate = null
}

// 工作循环
function workLoopConcurrent() {
  // 当有工作且未超时时继续执行
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress)
  }
}

function performUnitOfWork(unitOfWork) {
  const current = unitOfWork.alternate

  // 开始工作
  let next = beginWork(current, unitOfWork, renderLanes)

  unitOfWork.memoizedProps = unitOfWork.pendingProps

  if (next === null) {
    // 没有子节点,完成工作
    completeUnitOfWork(unitOfWork)
  } else {
    // 继续处理子节点
    workInProgress = next
  }
}

// 开始阶段
function beginWork(current, workInProgress, renderLanes) {
  if (current !== null) {
    const oldProps = current.memoizedProps
    const newProps = workInProgress.pendingProps

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged()
    ) {
      didReceiveUpdate = true
    } else {
      // 可以复用
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes
      )

      if (!hasScheduledUpdateOrContext) {
        didReceiveUpdate = false
        return attemptEarlyBailout(current, workInProgress, renderLanes)
      }

      didReceiveUpdate = false
    }
  } else {
    didReceiveUpdate = false
  }

  // 清除优先级
  workInProgress.lanes = NoLanes

  // 根据不同类型处理
  switch (workInProgress.tag) {
    case FunctionComponent: {
      const Component = workInProgress.type
      const unresolvedProps = workInProgress.pendingProps
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        unresolvedProps,
        renderLanes
      )
    }
    case ClassComponent: {
      const Component = workInProgress.type
      const unresolvedProps = workInProgress.pendingProps
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        unresolvedProps,
        renderLanes
      )
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes)
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes)
    // ... 其他类型
  }
}

// 完成阶段
function completeUnitOfWork(unitOfWork) {
  let completedWork = unitOfWork

  do {
    const current = completedWork.alternate
    const returnFiber = completedWork.return

    // 完成工作
    const next = completeWork(current, completedWork, renderLanes)

    if (next !== null) {
      // 发现新工作
      workInProgress = next
      return
    }

    // 收集副作用
    if (returnFiber !== null) {
      if (returnFiber.firstEffect === null) {
        returnFiber.firstEffect = completedWork.firstEffect
      }

      if (completedWork.lastEffect !== null) {
        if (returnFiber.lastEffect !== null) {
          returnFiber.lastEffect.nextEffect = completedWork.firstEffect
        }
        returnFiber.lastEffect = completedWork.lastEffect
      }

      const flags = completedWork.flags

      if (flags > PerformedWork) {
        if (returnFiber.lastEffect !== null) {
          returnFiber.lastEffect.nextEffect = completedWork
        } else {
          returnFiber.firstEffect = completedWork
        }
        returnFiber.lastEffect = completedWork
      }
    }

    const siblingFiber = completedWork.sibling

    if (siblingFiber !== null) {
      // 处理兄弟节点
      workInProgress = siblingFiber
      return
    }

    // 返回父节点
    completedWork = returnFiber
    workInProgress = completedWork
  } while (completedWork !== null)
}

6. React Hooks 原理

问题:useState 和 useEffect 的实现原理是什么?

Hooks 实现:

// Hook 数据结构
const hook = {
  memoizedState: null,  // 当前状态
  baseState: null,      // 基础状态
  baseQueue: null,      // 基础更新队列
  queue: null,          // 更新队列
  next: null            // 下一个 Hook
}

// 当前正在渲染的 Fiber
let currentlyRenderingFiber = null
// 当前工作中的 Hook
let workInProgressHook = null
// 当前 Hook 对应的旧 Hook
let currentHook = null

// useState 实现
function useState(initialState) {
  return useReducer(
    basicStateReducer,
    initialState
  )
}

function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action
}

function useReducer(reducer, initialArg, init) {
  const hook = mountWorkInProgressHook()

  let initialState
  if (init !== undefined) {
    initialState = init(initialArg)
  } else {
    initialState = initialArg
  }

  hook.memoizedState = hook.baseState = initialState

  const queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: initialState
  }

  hook.queue = queue

  const dispatch = (queue.dispatch = dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue
  ))

  return [hook.memoizedState, dispatch]
}

// 更新状态
function dispatchAction(fiber, queue, action) {
  const update = {
    action,
    next: null
  }

  // 构建环形链表
  const pending = queue.pending
  if (pending === null) {
    update.next = update
  } else {
    update.next = pending.next
    pending.next = update
  }
  queue.pending = update

  // 调度更新
  const alternate = fiber.alternate

  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 渲染期间的更新
    didScheduleRenderPhaseUpdateDuringThisPass = true
  } else {
    // 立即计算新状态(性能优化)
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      const lastRenderedReducer = queue.lastRenderedReducer
      if (lastRenderedReducer !== null) {
        try {
          const currentState = queue.lastRenderedState
          const eagerState = lastRenderedReducer(currentState, action)

          if (Object.is(eagerState, currentState)) {
            // 状态未变化,跳过更新
            return
          }
        } catch (error) {
          // 忽略错误
        }
      }
    }

    scheduleUpdateOnFiber(fiber, lane, eventTime)
  }
}

// useEffect 实现
function useEffect(create, deps) {
  return mountEffectImpl(
    PassiveEffect | PassiveStaticEffect,
    HookPassive,
    create,
    deps
  )
}

function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
  const hook = mountWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps

  currentlyRenderingFiber.flags |= fiberFlags

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps
  )
}

function pushEffect(tag, create, destroy, deps) {
  const effect = {
    tag,
    create,
    destroy,
    deps,
    next: null
  }

  let componentUpdateQueue = currentlyRenderingFiber.updateQueue

  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue()
    currentlyRenderingFiber.updateQueue = componentUpdateQueue
    componentUpdateQueue.lastEffect = effect.next = effect
  } else {
    const lastEffect = componentUpdateQueue.lastEffect
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect
    } else {
      const firstEffect = lastEffect.next
      lastEffect.next = effect
      effect.next = firstEffect
      componentUpdateQueue.lastEffect = effect
    }
  }

  return effect
}

// 更新时的 useEffect
function updateEffect(create, deps) {
  return updateEffectImpl(
    PassiveEffect,
    HookPassive,
    create,
    deps
  )
}

function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
  const hook = updateWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  let destroy = undefined

  if (currentHook !== null) {
    const prevEffect = currentHook.memoizedState
    destroy = prevEffect.destroy

    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 依赖未变化,不执行
        pushEffect(hookFlags, create, destroy, nextDeps)
        return
      }
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps
  )
}

// 执行 Effects
function commitHookEffectListMount(tag, finishedWork) {
  const updateQueue = finishedWork.updateQueue
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null

  if (lastEffect !== null) {
    const firstEffect = lastEffect.next
    let effect = firstEffect

    do {
      if ((effect.tag & tag) === tag) {
        const create = effect.create
        effect.destroy = create()
      }
      effect = effect.next
    } while (effect !== firstEffect)
  }
}

function commitHookEffectListUnmount(tag, finishedWork) {
  const updateQueue = finishedWork.updateQueue
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null

  if (lastEffect !== null) {
    const firstEffect = lastEffect.next
    let effect = firstEffect

    do {
      if ((effect.tag & tag) === tag) {
        const destroy = effect.destroy
        effect.destroy = undefined

        if (destroy !== undefined) {
          destroy()
        }
      }
      effect = effect.next
    } while (effect !== firstEffect)
  }
}

三、TypeScript 高级特性

7. TypeScript 类型系统

问题:如何实现高级类型推导和类型体操?

高级类型示例:

// 1. 深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : DeepReadonly<T[P]>
    : T[P]
}

// 2. 深度可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : DeepPartial<T[P]>
    : T[P]
}

// 3. 类型过滤
type Filter<T, U> = T extends U ? T : never

type Example1 = Filter<'a' | 'b' | 'c' | 1 | 2, string>  // 'a' | 'b' | 'c'

// 4. 获取函数返回类型
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any

// 5. 获取 Promise 的值类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T

type Example2 = UnwrapPromise<Promise<string>>  // string

// 6. 元组转联合类型
type TupleToUnion<T extends any[]> = T[number]

type Example3 = TupleToUnion<[string, number, boolean]>  // string | number | boolean

// 7. 联合类型转交叉类型
type UnionToIntersection<U> = (
  U extends any ? (k: U) => void : never
) extends (k: infer I) => void
  ? I
  : never

type Example4 = UnionToIntersection<{ a: string } | { b: number }>
// { a: string } & { b: number }

// 8. 必选属性
type Required<T> = {
  [P in keyof T]-?: T[P]
}

// 9. 挑选属性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

// 10. 排除属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

// 11. 函数参数类型
type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never

// 12. 构造函数参数类型
type ConstructorParameters<T extends new (...args: any) => any> =
  T extends new (...args: infer P) => any ? P : never

// 13. 递归对象路径
type PathImpl<T, K extends keyof T> = K extends string
  ? T[K] extends Record<string, any>
    ? T[K] extends ArrayLike<any>
      ? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>>}`
      : K | `${K}.${PathImpl<T[K], keyof T[K]>}`
    : K
  : never

type Path<T> = PathImpl<T, keyof T> | keyof T

// 使用示例
interface User {
  name: string
  age: number
  address: {
    city: string
    street: {
      name: string
      number: number
    }
  }
  hobbies: string[]
}

type UserPath = Path<User>
// 'name' | 'age' | 'address' | 'address.city' | 'address.street' |
// 'address.street.name' | 'address.street.number' | 'hobbies'

// 14. 根据路径获取类型
type PathValue<T, P extends string> = P extends keyof T
  ? T[P]
  : P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
    ? PathValue<T[K], Rest>
    : never
  : never

type CityType = PathValue<User, 'address.city'>  // string
type StreetNumberType = PathValue<User, 'address.street.number'>  // number

8. TypeScript 装饰器

问题:实现常用的装饰器模式。

装饰器实现:

// 1. 方法装饰器 - 日志记录
function Log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args)
    const result = originalMethod.apply(this, args)
    console.log(`Result:`, result)
    return result
  }

  return descriptor
}

// 2. 方法装饰器 - 性能测量
function Measure(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value

  descriptor.value = async function (...args: any[]) {
    const start = performance.now()
    const result = await originalMethod.apply(this, args)
    const end = performance.now()
    console.log(`${propertyKey} took ${end - start}ms`)
    return result
  }

  return descriptor
}

// 3. 方法装饰器 - 缓存
function Memoize(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value
  const cache = new Map<string, any>()

  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args)

    if (cache.has(key)) {
      return cache.get(key)
    }

    const result = originalMethod.apply(this, args)
    cache.set(key, result)
    return result
  }

  return descriptor
}

// 4. 方法装饰器 - 防抖
function Debounce(delay: number) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value
    let timeoutId: NodeJS.Timeout

    descriptor.value = function (...args: any[]) {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => {
        originalMethod.apply(this, args)
      }, delay)
    }

    return descriptor
  }
}

// 5. 方法装饰器 - 节流
function Throttle(delay: number) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value
    let lastTime = 0

    descriptor.value = function (...args: any[]) {
      const now = Date.now()

      if (now - lastTime >= delay) {
        lastTime = now
        return originalMethod.apply(this, args)
      }
    }

    return descriptor
  }
}

// 6. 属性装饰器 - 验证
function MinLength(length: number) {
  return function (target: any, propertyKey: string) {
    let value: string

    const getter = function () {
      return value
    }

    const setter = function (newVal: string) {
      if (newVal.length < length) {
        throw new Error(`${propertyKey} must be at least ${length} characters`)
      }
      value = newVal
    }

    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    })
  }
}

// 7. 类装饰器 - 单例模式
function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
  let instance: T | null = null

  return new Proxy(constructor, {
    construct(target, args) {
      if (!instance) {
        instance = new target(...args)
      }
      return instance
    }
  })
}

// 8. 参数装饰器 - 验证
const requiredMetadataKey = Symbol('required')

function Required(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  let existingRequiredParameters: number[] =
    Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || []
  existingRequiredParameters.push(parameterIndex)
  Reflect.defineMetadata(
    requiredMetadataKey,
    existingRequiredParameters,
    target,
    propertyKey
  )
}

function Validate(
  target: any,
  propertyName: string,
  descriptor: PropertyDescriptor
) {
  const method = descriptor.value

  descriptor.value = function (...args: any[]) {
    const requiredParameters: number[] = Reflect.getOwnMetadata(
      requiredMetadataKey,
      target,
      propertyName
    )

    if (requiredParameters) {
      for (const parameterIndex of requiredParameters) {
        if (
          parameterIndex >= args.length ||
          args[parameterIndex] === undefined ||
          args[parameterIndex] === null
        ) {
          throw new Error(`Missing required argument at position ${parameterIndex}`)
        }
      }
    }

    return method.apply(this, args)
  }
}

// 使用示例
class UserService {
  @Log
  @Measure
  async getUser(id: number) {
    // 模拟异步操作
    await new Promise(resolve => setTimeout(resolve, 100))
    return { id, name: 'User ' + id }
  }

  @Memoize
  calculateExpensiveValue(n: number): number {
    console.log('Calculating...')
    return n * n
  }

  @Debounce(300)
  handleSearch(query: string) {
    console.log('Searching for:', query)
  }

  @Throttle(1000)
  handleScroll() {
    console.log('Scrolling...')
  }

  @Validate
  createUser(@Required name: string, @Required email: string, age?: number) {
    console.log('Creating user:', { name, email, age })
  }
}

@Singleton
class Database {
  constructor() {
    console.log('Database instance created')
  }
}

class User {
  @MinLength(3)
  username: string

  constructor(username: string) {
    this.username = username
  }
}

四、浏览器原理

9. 浏览器渲染流程

问题:详细描述浏览器从 HTML 到像素的完整渲染流程。

渲染流程:

  1. 解析 HTML → DOM 树
  2. 解析 CSS → CSSOM 树
  3. 合并 → 渲染树(Render Tree)
  4. 布局(Layout/Reflow)
  5. 绘制(Paint)
  6. 合成(Composite)

关键优化点:

// 1. 减少重排(Reflow)
// 不好的做法
const el = document.getElementById('element')
el.style.width = '100px'
el.style.height = '100px'
el.style.margin = '10px'
// 触发 3 次重排

// 好的做法
el.style.cssText = 'width: 100px; height: 100px; margin: 10px;'
// 只触发 1 次重排

// 使用 class
el.className = 'new-class'

// 2. 批量 DOM 操作
// 不好的做法
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li')
  li.textContent = i
  document.getElementById('list').appendChild(li)
}
// 触发 1000 次重排

// 好的做法:使用 DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li')
  li.textContent = i
  fragment.appendChild(li)
}
document.getElementById('list').appendChild(fragment)
// 只触发 1 次重排

// 3. 读写分离
// 不好的做法
const width1 = div1.clientWidth  // 读
div1.style.width = width1 + 10 + 'px'  // 写
const width2 = div2.clientWidth  // 读(触发强制重排)
div2.style.width = width2 + 10 + 'px'  // 写

// 好的做法:先读后写
const width1 = div1.clientWidth
const width2 = div2.clientWidth
div1.style.width = width1 + 10 + 'px'
div2.style.width = width2 + 10 + 'px'

// 4. 使用 transform 代替 top/left
// 不好的做法(触发重排)
element.style.left = element.offsetLeft + 10 + 'px'

// 好的做法(只触发合成)
element.style.transform = 'translateX(10px)'

// 5. 使用 will-change 提示浏览器
.moving-element {
  will-change: transform;
}

// 6. 使用 requestAnimationFrame
function animate() {
  element.style.transform = `translateX(${x}px)`
  x += 1

  if (x < 100) {
    requestAnimationFrame(animate)
  }
}
requestAnimationFrame(animate)

10. 事件循环机制

问题:详细说明浏览器和 Node.js 的事件循环差异。

浏览器事件循环:

// 事件循环执行顺序
// 1. 执行同步代码
// 2. 执行微任务队列
// 3. 执行一个宏任务
// 4. 执行微任务队列
// 5. 渲染(如果需要)
// 6. 回到步骤 3

console.log('1')

setTimeout(() => {
  console.log('2')
  Promise.resolve().then(() => {
    console.log('3')
  })
}, 0)

new Promise((resolve) => {
  console.log('4')
  resolve()
}).then(() => {
  console.log('5')
}).then(() => {
  console.log('6')
})

console.log('7')

// 输出顺序:1, 4, 7, 5, 6, 2, 3

// 微任务:
// - Promise.then/catch/finally
// - MutationObserver
// - queueMicrotask
// - process.nextTick (Node.js)

// 宏任务:
// - setTimeout
// - setInterval
// - setImmediate (Node.js)
// - I/O
// - UI rendering
// - requestAnimationFrame

// 复杂示例
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

async1()

new Promise((resolve) => {
  console.log('promise1')
  resolve()
}).then(() => {
  console.log('promise2')
})

console.log('script end')

// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

Node.js 事件循环:

// Node.js 事件循环阶段
// 1. timers: setTimeout/setInterval
// 2. pending callbacks: 系统级回调
// 3. idle, prepare: 内部使用
// 4. poll: I/O 回调
// 5. check: setImmediate
// 6. close callbacks: 关闭回调

// process.nextTick 优先级高于微任务

console.log('start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

setImmediate(() => {
  console.log('setImmediate')
})

process.nextTick(() => {
  console.log('nextTick')
})

Promise.resolve().then(() => {
  console.log('promise')
})

console.log('end')

// 输出顺序:
// start
// end
// nextTick
// promise
// setTimeout
// setImmediate

// 注意:setTimeout 和 setImmediate 的顺序可能变化
// 在 I/O 回调中,setImmediate 总是先于 setTimeout

11. 浏览器缓存策略

问题:详细说明强缓存和协商缓存的工作原理。

缓存策略:

// 1. 强缓存
// Cache-Control (HTTP/1.1,优先级高)
Cache-Control: max-age=3600  // 缓存 1 小时
Cache-Control: no-cache      // 每次使用前验证
Cache-Control: no-store      // 不缓存
Cache-Control: public        // 可被任何缓存存储
Cache-Control: private       // 只能被浏览器缓存

// Expires (HTTP/1.0)
Expires: Wed, 21 Oct 2025 07:28:00 GMT

// 2. 协商缓存
// Last-Modified / If-Modified-Since
// 服务器响应
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
// 客户端请求
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

// ETag / If-None-Match (优先级高)
// 服务器响应
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// 客户端请求
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

// 3. 缓存决策树实现
class CacheStrategy {
  constructor() {
    this.cache = new Map()
  }

  // 设置缓存
  set(key, value, options = {}) {
    const {
      maxAge = 3600,        // 默认 1 小时
      staleWhileRevalidate = 0,
      mustRevalidate = false
    } = options

    const item = {
      value,
      timestamp: Date.now(),
      maxAge,
      staleWhileRevalidate,
      mustRevalidate,
      etag: this.generateETag(value)
    }

    this.cache.set(key, item)
  }

  // 获取缓存
  async get(key, fetchFn) {
    const item = this.cache.get(key)

    if (!item) {
      // 缓存不存在,获取新数据
      const value = await fetchFn()
      this.set(key, value)
      return { value, fromCache: false }
    }

    const age = Date.now() - item.timestamp

    // 强缓存未过期
    if (age < item.maxAge * 1000) {
      return { value: item.value, fromCache: true, fresh: true }
    }

    // 在 staleWhileRevalidate 期间
    if (age < (item.maxAge + item.staleWhileRevalidate) * 1000) {
      // 返回旧数据,后台刷新
      this.revalidateInBackground(key, fetchFn)
      return { value: item.value, fromCache: true, fresh: false }
    }

    // 缓存过期,需要重新验证
    if (item.mustRevalidate) {
      const isModified = await this.checkModified(key, item.etag)

      if (!isModified) {
        // 304 Not Modified
        item.timestamp = Date.now()
        return { value: item.value, fromCache: true, revalidated: true }
      }
    }

    // 获取新数据
    const value = await fetchFn()
    this.set(key, value)
    return { value, fromCache: false }
  }

  // 生成 ETag
  generateETag(value) {
    const str = JSON.stringify(value)
    let hash = 0
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i)
      hash = ((hash << 5) - hash) + char
      hash = hash & hash
    }
    return hash.toString(36)
  }

  // 后台重新验证
  async revalidateInBackground(key, fetchFn) {
    try {
      const value = await fetchFn()
      this.set(key, value)
    } catch (error) {
      console.error('Background revalidation failed:', error)
    }
  }

  // 检查是否修改
  async checkModified(key, etag) {
    // 实际应该向服务器发送请求
    // 这里简化处理
    return Math.random() > 0.5
  }
}

// 使用示例
const cache = new CacheStrategy()

async function fetchUserData(id) {
  return cache.get(`user:${id}`, async () => {
    const response = await fetch(`/api/users/${id}`)
    return response.json()
  })
}

// 4. Service Worker 缓存策略
// Cache First (缓存优先)
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request)
    })
  )
})

// Network First (网络优先)
self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request)
      .then(response => {
        const responseClone = response.clone()
        caches.open('v1').then(cache => {
          cache.put(event.request, responseClone)
        })
        return response
      })
      .catch(() => {
        return caches.match(event.request)
      })
  )
})

// Stale While Revalidate (返回缓存,后台更新)
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.open('v1').then(cache => {
      return cache.match(event.request).then(response => {
        const fetchPromise = fetch(event.request).then(networkResponse => {
          cache.put(event.request, networkResponse.clone())
          return networkResponse
        })

        return response || fetchPromise
      })
    })
  )
})

五、性能优化

12. 首屏加载优化

问题:如何优化首屏加载时间?提供具体方案。

优化方案:

// 1. 代码分割 (Code Splitting)
// 路由懒加载
const Home = () => import('./views/Home.vue')
const About = () => import('./views/About.vue')

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

// React 代码分割
import { lazy, Suspense } from 'react'

const LazyComponent = lazy(() => import('./LazyComponent'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  )
}

// 2. 预加载 (Preload/Prefetch)
// HTML
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="critical.js" as="script">
<link rel="prefetch" href="next-page.js">

// JavaScript
const link = document.createElement('link')
link.rel = 'prefetch'
link.href = '/next-page.js'
document.head.appendChild(link)

// 3. 资源压缩
// Webpack 配置
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true
          }
        }
      }),
      new CssMinimizerPlugin()
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        },
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  }
}

// 4. 图片优化
// 响应式图片
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg">
  <img src="image.jpg" alt="Description">
</picture>

// 懒加载图片
class LazyImageLoader {
  constructor(selector = '.lazy-image') {
    this.images = document.querySelectorAll(selector)
    this.observer = null
    this.init()
  }

  init() {
    if ('IntersectionObserver' in window) {
      this.observer = new IntersectionObserver(
        entries => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              this.loadImage(entry.target)
              this.observer.unobserve(entry.target)
            }
          })
        },
        {
          rootMargin: '50px'
        }
      )

      this.images.forEach(img => this.observer.observe(img))
    } else {
      // 降级方案
      this.images.forEach(img => this.loadImage(img))
    }
  }

  loadImage(img) {
    const src = img.dataset.src
    const srcset = img.dataset.srcset

    if (src) {
      img.src = src
    }

    if (srcset) {
      img.srcset = srcset
    }

    img.classList.add('loaded')
  }
}

// 使用
new LazyImageLoader()

// 5. 关键 CSS 内联
// 提取关键 CSS
const critical = require('critical')

critical.generate({
  inline: true,
  base: 'dist/',
  src: 'index.html',
  target: {
    html: 'index-critical.html'
  },
  width: 1300,
  height: 900
})

// 6. DNS 预解析
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="preconnect" href="//api.example.com">

// 7. HTTP/2 Server Push
// 服务器配置
Link: </styles.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script

// 8. 骨架屏
const SkeletonScreen = () => {
  return (
    <div className="skeleton">
      <div className="skeleton-header"></div>
      <div className="skeleton-content">
        <div className="skeleton-line"></div>
        <div className="skeleton-line"></div>
        <div className="skeleton-line"></div>
      </div>
    </div>
  )
}

// CSS
.skeleton-line {
  height: 16px;
  margin: 10px 0;
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}

@keyframes loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

// 9. SSR/SSG
// Next.js SSG
export async function getStaticProps() {
  const data = await fetchData()

  return {
    props: { data },
    revalidate: 60  // ISR: 每 60 秒重新生成
  }
}

// Next.js SSR
export async function getServerSideProps(context) {
  const data = await fetchData()

  return {
    props: { data }
  }
}

// 10. 性能监控
class PerformanceMonitor {
  constructor() {
    this.metrics = {}
    this.observe()
  }

  observe() {
    // 页面加载性能
    if ('PerformanceObserver' in window) {
      // FCP (First Contentful Paint)
      new PerformanceObserver(list => {
        for (const entry of list.getEntries()) {
          if (entry.name === 'first-contentful-paint') {
            this.metrics.fcp = entry.startTime
            this.report('fcp', entry.startTime)
          }
        }
      }).observe({ entryTypes: ['paint'] })

      // LCP (Largest Contentful Paint)
      new PerformanceObserver(list => {
        const entries = list.getEntries()
        const lastEntry = entries[entries.length - 1]
        this.metrics.lcp = lastEntry.renderTime || lastEntry.loadTime
        this.report('lcp', this.metrics.lcp)
      }).observe({ entryTypes: ['largest-contentful-paint'] })

      // FID (First Input Delay)
      new PerformanceObserver(list => {
        for (const entry of list.getEntries()) {
          this.metrics.fid = entry.processingStart - entry.startTime
          this.report('fid', this.metrics.fid)
        }
      }).observe({ entryTypes: ['first-input'] })

      // CLS (Cumulative Layout Shift)
      let clsScore = 0
      new PerformanceObserver(list => {
        for (const entry of list.getEntries()) {
          if (!entry.hadRecentInput) {
            clsScore += entry.value
            this.metrics.cls = clsScore
            this.report('cls', clsScore)
          }
        }
      }).observe({ entryTypes: ['layout-shift'] })
    }

    // 传统性能指标
    window.addEventListener('load', () => {
      const timing = performance.timing

      this.metrics.dns = timing.domainLookupEnd - timing.domainLookupStart
      this.metrics.tcp = timing.connectEnd - timing.connectStart
      this.metrics.ttfb = timing.responseStart - timing.requestStart
      this.metrics.download = timing.responseEnd - timing.responseStart
      this.metrics.domParse = timing.domInteractive - timing.domLoading
      this.metrics.domReady = timing.domContentLoadedEventEnd - timing.navigationStart
      this.metrics.load = timing.loadEventEnd - timing.navigationStart

      this.reportAll()
    })
  }

  report(metric, value) {
    // 发送到分析服务
    console.log(`${metric}: ${value}ms`)

    // 实际应该发送到后端
    // navigator.sendBeacon('/analytics', JSON.stringify({ metric, value }))
  }

  reportAll() {
    console.table(this.metrics)
  }
}

// 使用
new PerformanceMonitor()

13. 长列表优化

问题:如何优化包含万条数据的列表渲染?

虚拟滚动实现:

// 虚拟滚动组件
class VirtualScroller {
  constructor(options) {
    this.container = options.container
    this.items = options.items
    this.itemHeight = options.itemHeight
    this.renderItem = options.renderItem
    this.buffer = options.buffer || 3

    this.scrollTop = 0
    this.containerHeight = this.container.clientHeight

    this.init()
  }

  init() {
    // 创建容器
    this.phantom = document.createElement('div')
    this.phantom.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: ${this.items.length * this.itemHeight}px;
      z-index: -1;
    `

    this.content = document.createElement('div')
    this.content.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
    `

    this.container.style.cssText = `
      position: relative;
      overflow-y: auto;
    `

    this.container.appendChild(this.phantom)
    this.container.appendChild(this.content)

    // 监听滚动
    this.container.addEventListener('scroll', () => {
      this.scrollTop = this.container.scrollTop
      this.render()
    })

    // 初始渲染
    this.render()
  }

  render() {
    // 计算可见范围
    const start = Math.floor(this.scrollTop / this.itemHeight)
    const end = Math.ceil((this.scrollTop + this.containerHeight) / this.itemHeight)

    // 添加缓冲区
    const visibleStart = Math.max(0, start - this.buffer)
    const visibleEnd = Math.min(this.items.length, end + this.buffer)

    // 计算偏移
    const offsetY = visibleStart * this.itemHeight

    // 渲染可见项
    const fragment = document.createDocumentFragment()

    for (let i = visibleStart; i < visibleEnd; i++) {
      const item = this.renderItem(this.items[i], i)
      item.style.cssText = `
        position: absolute;
        top: ${i * this.itemHeight}px;
        left: 0;
        right: 0;
        height: ${this.itemHeight}px;
      `
      fragment.appendChild(item)
    }

    this.content.innerHTML = ''
    this.content.appendChild(fragment)
  }

  // 更新数据
  update(items) {
    this.items = items
    this.phantom.style.height = `${items.length * this.itemHeight}px`
    this.render()
  }

  // 滚动到指定项
  scrollToIndex(index) {
    this.container.scrollTop = index * this.itemHeight
  }
}

// 使用示例
const container = document.getElementById('list-container')
const items = Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  text: `Item ${i}`
}))

const virtualScroller = new VirtualScroller({
  container,
  items,
  itemHeight: 50,
  renderItem: (item, index) => {
    const div = document.createElement('div')
    div.className = 'list-item'
    div.textContent = item.text
    div.dataset.index = index
    return div
  }
})

// React 虚拟滚动组件
import { useState, useRef, useEffect } from 'react'

function VirtualList({ items, itemHeight, height, renderItem }) {
  const [scrollTop, setScrollTop] = useState(0)
  const containerRef = useRef(null)

  const buffer = 3
  const start = Math.max(0, Math.floor(scrollTop / itemHeight) - buffer)
  const end = Math.min(
    items.length,
    Math.ceil((scrollTop + height) / itemHeight) + buffer
  )

  const visibleItems = items.slice(start, end)
  const offsetY = start * itemHeight

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop)
  }

  return (
    <div
      ref={containerRef}
      style={{
        height,
        overflow: 'auto',
        position: 'relative'
      }}
      onScroll={handleScroll}
    >
      <div
        style={{
          height: items.length * itemHeight,
          position: 'relative'
        }}
      >
        <div
          style={{
            transform: `translateY(${offsetY}px)`
          }}
        >
          {visibleItems.map((item, index) =>
            renderItem(item, start + index)
          )}
        </div>
      </div>
    </div>
  )
}

// 使用
function App() {
  const items = Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    text: `Item ${i}`
  }))

  return (
    <VirtualList
      items={items}
      itemHeight={50}
      height={600}
      renderItem={(item, index) => (
        <div
          key={item.id}
          style={{
            height: 50,
            borderBottom: '1px solid #eee'
          }}
        >
          {item.text}
        </div>
      )}
    />
  )
}

// Vue 虚拟滚动组件
import { ref, computed, onMounted } from 'vue'

export default {
  props: {
    items: Array,
    itemHeight: Number,
    height: Number
  },
  setup(props, { slots }) {
    const scrollTop = ref(0)
    const containerRef = ref(null)

    const buffer = 3

    const visibleRange = computed(() => {
      const start = Math.max(
        0,
        Math.floor(scrollTop.value / props.itemHeight) - buffer
      )
      const end = Math.min(
        props.items.length,
        Math.ceil((scrollTop.value + props.height) / props.itemHeight) + buffer
      )

      return { start, end }
    })

    const visibleItems = computed(() => {
      const { start, end } = visibleRange.value
      return props.items.slice(start, end).map((item, index) => ({
        item,
        index: start + index
      }))
    })

    const offsetY = computed(() => {
      return visibleRange.value.start * props.itemHeight
    })

    const handleScroll = (e) => {
      scrollTop.value = e.target.scrollTop
    }

    return {
      containerRef,
      visibleItems,
      offsetY,
      handleScroll
    }
  },
  template: `
    <div
      ref="containerRef"
      :style="{
        height: height + 'px',
        overflow: 'auto',
        position: 'relative'
      }"
      @scroll="handleScroll"
    >
      <div :style="{ height: items.length * itemHeight + 'px' }">
        <div :style="{ transform: 'translateY(' + offsetY + 'px)' }">
          <div
            v-for="{ item, index } in visibleItems"
            :key="index"
            :style="{ height: itemHeight + 'px' }"
          >
            <slot :item="item" :index="index"></slot>
          </div>
        </div>
      </div>
    </div>
  `
}

六、网络优化

14. HTTP 优化策略

问题:如何优化 HTTP 请求性能?

优化方案:

// 1. 请求合并
class RequestBatcher {
  constructor(batchFn, delay = 10) {
    this.batchFn = batchFn
    this.delay = delay
    this.queue = []
    this.timer = null
  }

  add(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject })

      if (!this.timer) {
        this.timer = setTimeout(() => {
          this.flush()
        }, this.delay)
      }
    })
  }

  async flush() {
    if (this.queue.length === 0) return

    const batch = this.queue.splice(0)
    this.timer = null

    try {
      const requests = batch.map(item => item.request)
      const results = await this.batchFn(requests)

      batch.forEach((item, index) => {
        item.resolve(results[index])
      })
    } catch (error) {
      batch.forEach(item => {
        item.reject(error)
      })
    }
  }
}

// 使用示例
const userBatcher = new RequestBatcher(async (userIds) => {
  const response = await fetch('/api/users/batch', {
    method: 'POST',
    body: JSON.stringify({ ids: userIds })
  })
  return response.json()
})

// 多个请求会被合并
const user1 = await userBatcher.add(1)
const user2 = await userBatcher.add(2)
const user3 = await userBatcher.add(3)
// 实际只发送一个请求: POST /api/users/batch { ids: [1, 2, 3] }

// 2. 请求去重
class RequestDeduplicator {
  constructor() {
    this.pending = new Map()
  }

  async request(key, fetchFn) {
    // 如果已有相同请求正在进行,返回该请求的 Promise
    if (this.pending.has(key)) {
      return this.pending.get(key)
    }

    const promise = fetchFn()
      .finally(() => {
        this.pending.delete(key)
      })

    this.pending.set(key, promise)
    return promise
  }
}

// 使用
const deduplicator = new RequestDeduplicator()

// 多个组件同时请求相同数据,只会发送一次请求
const data1 = await deduplicator.request('user:1', () =>
  fetch('/api/users/1').then(r => r.json())
)

const data2 = await deduplicator.request('user:1', () =>
  fetch('/api/users/1').then(r => r.json())
)

// data1 和 data2 是同一个 Promise

// 3. 请求重试
async function fetchWithRetry(url, options = {}, retries = 3) {
  const {
    retryDelay = 1000,
    retryOn = [500, 502, 503, 504],
    ...fetchOptions
  } = options

  for (let i = 0; i <= retries; i++) {
    try {
      const response = await fetch(url, fetchOptions)

      if (response.ok || !retryOn.includes(response.status)) {
        return response
      }

      if (i < retries) {
        await new Promise(resolve =>
          setTimeout(resolve, retryDelay * Math.pow(2, i))
        )
      }
    } catch (error) {
      if (i === retries) {
        throw error
      }

      await new Promise(resolve =>
        setTimeout(resolve, retryDelay * Math.pow(2, i))
      )
    }
  }
}

// 使用
const response = await fetchWithRetry('/api/data', {
  method: 'GET',
  retries: 3,
  retryDelay: 1000,
  retryOn: [500, 502, 503, 504]
})

// 4. 请求取消
class CancellableRequest {
  constructor() {
    this.pendingRequests = new Map()
  }

  async fetch(key, url, options = {}) {
    // 取消之前的请求
    this.cancel(key)

    const controller = new AbortController()
    this.pendingRequests.set(key, controller)

    try {
      const response = await fetch(url, {
        ...options,
        signal: controller.signal
      })

      this.pendingRequests.delete(key)
      return response
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('Request cancelled:', key)
      }
      throw error
    }
  }

  cancel(key) {
    const controller = this.pendingRequests.get(key)
    if (controller) {
      controller.abort()
      this.pendingRequests.delete(key)
    }
  }

  cancelAll() {
    this.pendingRequests.forEach(controller => controller.abort())
    this.pendingRequests.clear()
  }
}

// 使用
const cancellableRequest = new CancellableRequest()

// 搜索场景:用户快速输入时取消之前的请求
function handleSearch(query) {
  cancellableRequest.fetch('search', `/api/search?q=${query}`)
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => {
      if (error.name !== 'AbortError') {
        console.error(error)
      }
    })
}

// 5. 并发控制
class ConcurrencyController {
  constructor(limit = 6) {
    this.limit = limit
    this.running = 0
    this.queue = []
  }

  async add(fn) {
    if (this.running >= this.limit) {
      await new Promise(resolve => this.queue.push(resolve))
    }

    this.running++

    try {
      return await fn()
    } finally {
      this.running--

      const resolve = this.queue.shift()
      if (resolve) {
        resolve()
      }
    }
  }
}

// 使用
const controller = new ConcurrencyController(3)

const urls = Array.from({ length: 20 }, (_, i) => `/api/data/${i}`)

const promises = urls.map(url =>
  controller.add(() => fetch(url).then(r => r.json()))
)

const results = await Promise.all(promises)

// 6. 超时控制
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController()
  const id = setTimeout(() => controller.abort(), timeout)

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    })
    clearTimeout(id)
    return response
  } catch (error) {
    clearTimeout(id)
    if (error.name === 'AbortError') {
      throw new Error('Request timeout')
    }
    throw error
  }
}

// 使用
try {
  const response = await fetchWithTimeout('/api/data', {}, 3000)
  const data = await response.json()
} catch (error) {
  console.error(error.message)
}

15. WebSocket 优化

问题:如何实现可靠的 WebSocket 连接和断线重连?

WebSocket 封装:

class ReliableWebSocket {
  constructor(url, options = {}) {
    this.url = url
    this.options = {
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
      reconnectDecay: 1.5,
      maxReconnectAttempts: null,
      heartbeatInterval: 30000,
      heartbeatMessage: 'ping',
      ...options
    }

    this.ws = null
    this.reconnectAttempts = 0
    this.reconnectTimer = null
    this.heartbeatTimer = null
    this.forcedClose = false
    this.listeners = new Map()

    this.connect()
  }

  connect() {
    try {
      this.ws = new WebSocket(this.url)

      this.ws.onopen = (event) => {
        console.log('WebSocket connected')
        this.reconnectAttempts = 0
        this.startHeartbeat()
        this.emit('open', event)
      }

      this.ws.onmessage = (event) => {
        // 重置心跳
        this.resetHeartbeat()

        try {
          const data = JSON.parse(event.data)
          this.emit('message', data)
        } catch (error) {
          this.emit('message', event.data)
        }
      }

      this.ws.onerror = (event) => {
        console.error('WebSocket error:', event)
        this.emit('error', event)
      }

      this.ws.onclose = (event) => {
        console.log('WebSocket closed:', event.code, event.reason)
        this.stopHeartbeat()
        this.emit('close', event)

        if (!this.forcedClose) {
          this.reconnect()
        }
      }
    } catch (error) {
      console.error('WebSocket connection failed:', error)
      this.reconnect()
    }
  }

  reconnect() {
    if (
      this.options.maxReconnectAttempts &&
      this.reconnectAttempts >= this.options.maxReconnectAttempts
    ) {
      console.error('Max reconnect attempts reached')
      this.emit('maxReconnect')
      return
    }

    this.reconnectAttempts++

    const interval = Math.min(
      this.options.reconnectInterval *
        Math.pow(this.options.reconnectDecay, this.reconnectAttempts - 1),
      this.options.maxReconnectInterval
    )

    console.log(`Reconnecting in ${interval}ms... (attempt ${this.reconnectAttempts})`)

    this.reconnectTimer = setTimeout(() => {
      this.emit('reconnecting', this.reconnectAttempts)
      this.connect()
    }, interval)
  }

  startHeartbeat() {
    this.stopHeartbeat()

    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.send(this.options.heartbeatMessage)
      }
    }, this.options.heartbeatInterval)
  }

  resetHeartbeat() {
    this.stopHeartbeat()
    this.startHeartbeat()
  }

  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer)
      this.heartbeatTimer = null
    }
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      const message = typeof data === 'string' ? data : JSON.stringify(data)
      this.ws.send(message)
      return true
    }

    console.warn('WebSocket is not open')
    return false
  }

  close(code = 1000, reason = 'Normal closure') {
    this.forcedClose = true

    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
    }

    this.stopHeartbeat()

    if (this.ws) {
      this.ws.close(code, reason)
    }
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, [])
    }
    this.listeners.get(event).push(callback)
  }

  off(event, callback) {
    if (!this.listeners.has(event)) return

    const callbacks = this.listeners.get(event)
    const index = callbacks.indexOf(callback)

    if (index !== -1) {
      callbacks.splice(index, 1)
    }
  }

  emit(event, data) {
    if (!this.listeners.has(event)) return

    this.listeners.get(event).forEach(callback => {
      try {
        callback(data)
      } catch (error) {
        console.error('Error in event listener:', error)
      }
    })
  }
}

// 使用示例
const ws = new ReliableWebSocket('wss://api.example.com/ws', {
  reconnectInterval: 1000,
  maxReconnectAttempts: 10,
  heartbeatInterval: 30000
})

ws.on('open', () => {
  console.log('Connected to server')
  ws.send({ type: 'subscribe', channel: 'updates' })
})

ws.on('message', (data) => {
  console.log('Received:', data)
})

ws.on('error', (error) => {
  console.error('WebSocket error:', error)
})

ws.on('close', (event) => {
  console.log('Connection closed:', event)
})

ws.on('reconnecting', (attempt) => {
  console.log('Reconnecting... attempt', attempt)
})

// React Hook 封装
import { useEffect, useRef, useState } from 'react'

function useWebSocket(url, options = {}) {
  const [data, setData] = useState(null)
  const [status, setStatus] = useState('connecting')
  const wsRef = useRef(null)

  useEffect(() => {
    const ws = new ReliableWebSocket(url, options)
    wsRef.current = ws

    ws.on('open', () => setStatus('connected'))
    ws.on('message', (data) => setData(data))
    ws.on('close', () => setStatus('disconnected'))
    ws.on('reconnecting', () => setStatus('reconnecting'))

    return () => {
      ws.close()
    }
  }, [url])

  const send = (data) => {
    wsRef.current?.send(data)
  }

  return { data, status, send }
}

// 使用
function Chat() {
  const { data, status, send } = useWebSocket('wss://api.example.com/chat')

  const handleSend = () => {
    send({ type: 'message', content: 'Hello' })
  }

  return (
    <div>
      <div>Status: {status}</div>
      <div>Latest message: {JSON.stringify(data)}</div>
      <button onClick={handleSend}>Send</button>
    </div>
  )
}
Prev
11-系统设计面试方法论
Next
AI 与机器学习面试题