Skip to content

浏览器原理深入解析

更新时间:2025-02

目录

浏览器渲染流程详解

1. 完整渲染流程

javascript
// 浏览器渲染流程(从 HTML 到像素)
const renderingPipeline = {
  步骤1: 'HTML 解析 → DOM 树',
  步骤2: 'CSS 解析 → CSSOM 树',
  步骤3: 'DOM + CSSOM → 渲染树(Render Tree)',
  步骤4: '布局(Layout)→ 计算元素位置和大小',
  步骤5: '绘制(Paint)→ 绘制像素',
  步骤6: '合成(Composite)→ 合成图层'
}

// 1. HTML 解析
// 浏览器接收 HTML 字节流,转换为 DOM 树
const htmlParsing = `
  字节流 → 字符 → Token → 节点 → DOM 树
  
  示例:
  <html>
    <body>
      <div>Hello</div>
    </body>
  </html>
  
  解析为:
  Document
    └─ html
        └─ body
            └─ div
                └─ "Hello"
`

// 2. CSS 解析
// 浏览器解析 CSS,构建 CSSOM 树
const cssParsing = `
  CSS 字节流 → 字符 → Token → 节点 → CSSOM 树
  
  示例:
  body { font-size: 16px; }
  div { color: red; }
  
  解析为:
  body
    ├─ font-size: 16px
    └─ div
        └─ color: red
`


// 3. 构建渲染树
// 将 DOM 树和 CSSOM 树合并,生成渲染树
function buildRenderTree(dom, cssom) {
  const renderTree = []
  
  // 遍历 DOM 树
  function traverse(node) {
    // 跳过不可见元素(display: none)
    if (node.style.display === 'none') return
    
    // 跳过 <script>、<meta> 等不渲染的元素
    if (['SCRIPT', 'META', 'LINK'].includes(node.tagName)) return
    
    // 计算节点的最终样式
    const computedStyle = computeStyle(node, cssom)
    
    // 添加到渲染树
    renderTree.push({
      node,
      style: computedStyle
    })
    
    // 递归处理子节点
    node.children.forEach(child => traverse(child))
  }
  
  traverse(dom)
  return renderTree
}

// 4. 布局(Layout / Reflow)
// 计算每个元素的位置和大小
function layout(renderTree, v
ee.forEach(item => {
    const { node, style, box } = item
    
    // 绘制背景
    if (style.backgroundColor) {
      paintRecords.push({
        type: 'fillRect',
        x: box.x,
        y: box.y,
        width: box.width,
        height: box.height,
        color: style.backgroundColor
      })
    }
    
    // 绘制边框
    if (style.border) {
      paintRecords.push({
        type: 'strokeRect',
        x: box.x,
        y: box.y,
        width: box.width,
        height: box.height,
        color: style.borderColor,
        width: style.borderWidth
      })
    }
    
    // 绘制文本
    if (node.textContent) {
      paintRecords.push({
        type: 'fillText',
        text: node.textContent,
        x: box.x + style.paddingLeft,
        y: box.y + style.paddingTop,
        font: style.font,
        color: style.color
      })
    }
  })
  
  return paintRecords
}

// 6. 合成(Composite)
// 将多个图层合成为最终的页面
function composite(layers) {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  
  // 按 z-index 排序图层
  layers.sort((a, b) => a.zIndex - b.zIndex)
  
  // 逐层绘制
  layers.forEach(layer => {
    ctx.drawImage(layer.bitmap, layer.x, layer.y)
  })
  
  return canvas
}

2. 关键渲染路径优化

javascript
// 关键渲染路径(Critical Rendering Path)
// 从接收 HTML 到首次渲染的最短路径

// 1. 减少关键资源数量
// ❌ 阻塞渲染的 CSS
<link rel="stylesheet" href="styles.css">

// ✅ 非关键 CSS 异步加载
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">

// ✅ 内联关键 CSS
<style>
  /* 首屏关键样式 */
  .header { ... }
  .hero { ... }
</style>

// 2. 减少关键资源大小
// ✅ 压缩 CSS
/* 压缩前 */
body {
  margin: 0;
  padding: 0;
  font-family: Arial, sans-serif;
}

/* 压缩后 */
body{margin:0;padding:0;font-family:Arial,sans-serif}

// ✅ 移除未使用的 CSS
// 使用 PurgeCSS 或 UnCSS

// 3. 优化关键资源加载顺序
// ✅ 预加载关键资源
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="font.woff2" as="font" crossorigin>

// ✅ DNS 预解析
<link rel="dns-prefetch" href="https://api.example.com">

// ✅ 预连接
<link rel="preconnect" href="https://cdn.example.com">

// 4. JavaScript 优化
// ❌ 阻塞解析的脚本
<script src="app.js"></script>

// ✅ 异步加载
<script src="app.js" async></script>

// ✅ 延迟加载
<script src="app.js" defer></script>

// ✅ 模块化加载
<script type="module" src="app.js"></script>

// 5. 资源提示(Resource Hints)
// 预取(Prefetch):低优先级,空闲时加载
<link rel="prefetch" href="next-page.html">

// 预渲染(Prerender):提前渲染整个页面
<link rel="prerender" href="next-page.html">

// 6. 实战示例:优化首屏加载
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <!-- 1. 内联关键 CSS -->
  <style>
    /* 首屏关键样式 */
    body { margin: 0; font-family: Arial, sans-serif; }
    .header { height: 60px; background: #333; }
    .hero { height: 400px; background: #f0f0f0; }
  </style>
  
  <!-- 2. 预加载关键资源 -->
  <link rel="preload" href="hero-image.jpg" as="image">
  <link rel="preload" href="font.woff2" as="font" crossorigin>
  
  <!-- 3. 异步加载非关键 CSS -->
  <link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
  
  <!-- 4. DNS 预解析 -->
  <link rel="dns-prefetch" href="https://api.example.com">
</head>
<body>
  <header class="header">...</header>
  <section class="hero">...</section>
  
  <!-- 5. 延迟加载 JavaScript -->
  <script src="app.js" defer></script>
</body>
</html>

3. 渲染性能监控

javascript
// 使用 Performance API 监控渲染性能

// 1. 监控关键渲染指标
function measureRenderingMetrics() {
  // 获取 Navigation Timing
  const navigation = performance.getEntriesByType('navigation')[0]
  
  const metrics = {
    // DNS 查询时间
    dnsTime: navigation.domainLookupEnd - navigation.domainLookupStart,
    
    // TCP 连接时间
    tcpTime: navigation.connectEnd - navigation.connectStart,
    
    // 请求响应时间
    requestTime: navigation.responseEnd - navigation.requestStart,
    
    // DOM 解析时间
    domParseTime: navigation.domInteractive - navigation.domLoading,
    
    // 资源加载时间
    resourceLoadTime: navigation.loadEventStart - navigation.domContentLoadedEventEnd,
    
    // 首次渲染时间(FP)
    firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime,
    
    // 首次内容渲染时间(FCP)
    firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime,
    
    // DOM 内容加载完成时间
    domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
    
    // 页面完全加载时间
    loadComplete: navigation.loadEventEnd - navigation.loadEventStart
  }
  
  console.table(metrics)
  return metrics
}

// 2. 监控长任务(Long Tasks)
// 长任务:执行时间超过 50ms 的任务
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn('长任务检测:', {
      name: entry.name,
      duration: entry.duration,
      startTime: entry.startTime
    })
    
    // 上报到监控系统
    reportLongTask(entry)
  }
})

observer.observe({ entryTypes: ['longtask'] })

// 3. 监控布局偏移(Layout Shift)
const layoutShiftObserver = new PerformanceObserver((list) => {
  let cls = 0
  
  for (const entry of list.getEntries()) {
    // 只统计非用户交互导致的偏移
    if (!entry.hadRecentInput) {
      cls += entry.value
    }
  }
  
  console.log('累积布局偏移(CLS):', cls)
})

layoutShiftObserver.observe({ entryTypes: ['layout-shift'] })

// 4. 监控最大内容绘制(LCP)
const lcpObserver = new PerformanceObserver((list) => {
  const entries = list.getEntries()
  const lastEntry = entries[entries.length - 1]
  
  console.log('最大内容绘制(LCP):', {
    renderTime: lastEntry.renderTime,
    loadTime: lastEntry.loadTime,
    size: lastEntry.size,
    element: lastEntry.element
  })
})

lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] })

// 5. 监控首次输入延迟(FID)
const fidObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const fid = entry.processingStart - entry.startTime
    
    console.log('首次输入延迟(FID):', {
      delay: fid,
      name: entry.name,
      startTime: entry.startTime
    })
  }
})

fidObserver.observe({ entryTypes: ['first-input'] })

JavaScript 引擎原理(V8)

1. V8 引擎架构

javascript
// V8 引擎执行流程
const v8Pipeline = {
  步骤1: 'JavaScript 源代码',
  步骤2: 'Parser(解析器)→ AST(抽象语法树)',
  步骤3: 'Ignition(解释器)→ 字节码',
  步骤4: 'TurboFan(优化编译器)→ 机器码',
  步骤5: '执行机器码'
}

// 1. 解析(Parsing)
// 将 JavaScript 代码转换为 AST
function parseCode(code) {
  // 词法分析(Lexical Analysis)
  const tokens = lexer(code)
  
  // 语法分析(Syntax Analysis)
  const ast = parser(tokens)
  
  return ast
}

// 示例:解析函数声明
const code = 'function add(a, b) { return a + b; }'

// AST 结构
const ast = {
  type: 'FunctionDeclaration',
  id: { type: 'Identifier', name: 'add' },
  params: [
    { type: 'Identifier', name: 'a' },
    { type: 'Identifier', name: 'b' }
  ],
  body: {
    type: 'BlockStatement',
    body: [{
      type: 'ReturnStatement',
      argument: {
        type: 'BinaryExpression',
        operator: '+',
        left: { type: 'Identifier', name: 'a' },
        right: { type: 'Identifier', name: 'b' }
      }
    }]
  }
}

// 2. 字节码生成(Ignition)
// 将 AST 转换为字节码
function generateBytecode(ast) {
  const bytecode = []
  
  // 遍历 AST,生成字节码指令
  function visit(node) {
    switch (node.type) {
      case 'FunctionDeclaration':
        bytecode.push(['CreateClosure', node.id.name])
        visit(node.body)
        break
      
      case 'ReturnStatement':
        visit(node.argument)
        bytecode.push(['Return'])
        break
      
      case 'BinaryExpression':
        visit(node.left)
        visit(node.right)
        bytecode.push(['Add'])
        break
      
      case 'Identifier':
        bytecode.push(['LoadLocal', node.name])
        break
    }
  }
  
  visit(ast)
  return bytecode
}

// 3. JIT 编译(TurboFan)
// 热点代码优化为机器码
class JITCompiler {
  constructor() {
    this.hotFunctions = new Map() // 热点函数
    this.threshold = 100 // 优化阈值
  }
  
  // 记录函数调用次数
  recordCall(func) {
    const count = this.hotFunctions.get(func) || 0
    this.hotFunctions.set(func, count + 1)
    
    // 达到阈值,触发优化
    if (count + 1 >= this.threshold) {
      this.optimize(func)
    }
  }
  
  // 优化函数
  optimize(func) {
    console.log('优化函数:', func.name)
    
    // 1. 内联(Inlining)
    // 将小函数直接嵌入调用处
    
    // 2. 逃逸分析(Escape Analysis)
    // 分析对象是否逃逸出函数作用域
    
    // 3. 标量替换(Scalar Replacement)
    // 将对象拆分为基本类型
    
    // 4. 死代码消除(Dead Code Elimination)
    // 移除永远不会执行的代码
    
    // 5. 循环优化(Loop Optimization)
    // 循环展开、循环不变量外提
  }
}

// 4. 隐藏类(Hidden Classes)
// V8 使用隐藏类优化对象属性访问
class HiddenClass {
  constructor() {
    this.properties = new Map()
    this.transitions = new Map()
  }
  
  // 添加属性
  addProperty(name, offset) {
    // 创建新的隐藏类
    const newClass = new HiddenClass()
    newClass.properties = new Map(this.properties)
    newClass.properties.set(name, offset)
    
    // 记录转换
    this.transitions.set(name, newClass)
    
    return newClass
  }
}

// 示例:隐藏类优化
function Point(x, y) {
  this.x = x // 隐藏类 C0 → C1
  this.y = y // 隐藏类 C1 → C2
}

// ✅ 相同的属性添加顺序,共享隐藏类
const p1 = new Point(1, 2) // 使用 C2
const p2 = new Point(3, 4) // 使用 C2

// ❌ 不同的属性添加顺序,创建新的隐藏类
const p3 = {}
p3.y = 2 // 隐藏类 C0 → C3
p3.x = 1 // 隐藏类 C3 → C4(不同于 C2)

// 5. 内联缓存(Inline Caching)
// 缓存属性访问的位置
class InlineCache {
  constructor() {
    this.cache = new Map()
  }
  
  // 获取属性
  getProperty(obj, prop) {
    const hiddenClass = obj.__hiddenClass__
    
    // 检查缓存
    const cached = this.cache.get(hiddenClass)
    if (cached && cached.prop === prop) {
      // 缓存命中,直接返回偏移量
      return obj.__data__[cached.offset]
    }
    
    // 缓存未命中,查找属性
    const offset = hiddenClass.properties.get(prop)
    
    // 更新缓存
    this.cache.set(hiddenClass, { prop, offset })
    
    return obj.__data__[offset]
  }
}

// 6. 垃圾回收(Garbage Collection)
// V8 使用分代垃圾回收
class GarbageCollector {
  constructor() {
    this.newSpace = [] // 新生代(Young Generation)
    this.oldSpace = [] // 老生代(Old Generation)
  }
  
  // 新生代垃圾回收(Scavenge)
  scavenge() {
    const survivors = []
    
    // 标记-复制算法
    for (const obj of this.newSpace) {
      if (this.isReachable(obj)) {
        // 存活对象复制到 To-Space
        survivors.push(obj)
        
        // 晋升到老生代
        if (obj.age > 1) {
          this.oldSpace.push(obj)
        }
      }
    }
    
    // 清空 From-Space
    this.newSpace = survivors
  }
  
  // 老生代垃圾回收(Mark-Sweep-Compact)
  markSweepCompact() {
    // 1. 标记(Mark)
    const marked = new Set()
    this.mark(this.roots, marked)
    
    // 2. 清除(Sweep)
    this.oldSpace = this.oldSpace.filter(obj => marked.has(obj))
    
    // 3. 整理(Compact)
    this.compact()
  }
  
  // 标记可达对象
  mark(obj, marked) {
    if (marked.has(obj)) return
    
    marked.add(obj)
    
    // 递归标记引用的对象
    for (const ref of obj.references) {
      this.mark(ref, marked)
    }
  }
  
  // 整理内存碎片
  compact() {
    // 将存活对象移动到内存的一端
    // 更新所有引用
  }
}

2. V8 性能优化技巧

javascript
// 1. 避免动态添加/删除属性
// ❌ 动态添加属性,破坏隐藏类
function Point(x, y) {
  this.x = x
  this.y = y
}

const p = new Point(1, 2)
p.z = 3 // 创建新的隐藏类

// ✅ 在构造函数中定义所有属性
function Point(x, y, z) {
  this.x = x
  this.y = y
  this.z = z || 0 // 即使不使用,也要定义
}

// 2. 保持属性添加顺序一致
// ❌ 不同的添加顺序
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 2, a: 1 } // 不同的隐藏类

// ✅ 相同的添加顺序
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 3, b: 4 } // 相同的隐藏类

// 3. 避免使用 delete
// ❌ 使用 delete 删除属性
const obj = { a: 1, b: 2 }
delete obj.a // 破坏隐藏类

// ✅ 设置为 undefined
const obj = { a: 1, b: 2 }
obj.a = undefined // 保持隐藏类

// 4. 使用单态函数
// ❌ 多态函数(接受不同类型的参数)
function add(a, b) {
  return a + b
}

add(1, 2)       // 数字
add('a', 'b')   // 字符串
add({}, {})     // 对象

// ✅ 单态函数(只接受一种类型)
function addNumbers(a, b) {
  return a + b
}

addNumbers(1, 2)
addNumbers(3, 4)

// 5. 避免稀疏数组
// ❌ 稀疏数组
const arr = []
arr[0] = 1
arr[1000] = 2 // 稀疏数组,性能差

// ✅ 密集数组
const arr = [1, 2, 3, 4, 5]

// 6. 使用类型化数组
// ❌ 普通数组
const arr = [1, 2, 3, 4, 5]

// ✅ 类型化数组(性能更好)
const arr = new Int32Array([1, 2, 3, 4, 5])

// 7. 避免在循环中创建函数
// ❌ 循环中创建函数
for (let i = 0; i < 1000; i++) {
  setTimeout(() => console.log(i), 1000)
}

// ✅ 循环外创建函数
function logValue(value) {
  console.log(value)
}

for (let i = 0; i < 1000; i++) {
  setTimeout(() => logValue(i), 1000)
}

// 8. 使用对象池
// ✅ 对象池,减少 GC 压力
class ObjectPool {
  constructor(createFn, resetFn) {
    this.createFn = createFn
    this.resetFn = resetFn
    this.pool = []
  }
  
  acquire() {
    return this.pool.pop() || this.createFn()
  }
  
  release(obj) {
    this.resetFn(obj)
    this.pool.push(obj)
  }
}

// 使用示例
const pointPool = new ObjectPool(
  () => ({ x: 0, y: 0 }),
  (obj) => { obj.x = 0; obj.y = 0 }
)

const p1 = pointPool.acquire()
p1.x = 10
p1.y = 20

// 使用完毕,归还对象池
pointPool.release(p1)

垃圾回收机制

1. 垃圾回收算法

javascript
// 1. 引用计数(Reference Counting)
// 记录每个对象被引用的次数
class ReferenceCountingGC {
  constructor() {
    this.objects = new Map() // 对象 → 引用计数
  }
  
  // 创建对象
  create(obj) {
    this.objects.set(obj, 0)
    return obj
  }
  
  // 增加引用
  addRef(obj) {
    const count = this.objects.get(obj) || 0
    this.objects.set(obj, count + 1)
  }
  
  // 减少引用
  removeRef(obj) {
    const count = this.objects.get(obj)
    if (count === 1) {
      // 引用计数为 0,回收对象
      this.collect(obj)
    } else {
      this.objects.set(obj, count - 1)
    }
  }
  
  // 回收对象
  collect(obj) {
    // 递归减少引用的对象的计数
    for (const ref of obj.references || []) {
      this.removeRef(ref)
    }
    
    // 删除对象
    this.objects.delete(obj)
  }
}

// 问题:循环引用无法回收
const obj1 = {}
const obj2 = {}
obj1.ref = obj2
obj2.ref = obj1 // 循环引用,引用计数永远不为 0

// 2. 标记-清除(Mark-Sweep)
// 从根对象开始标记所有可达对象,然后清除未标记的对象
class MarkSweepGC {
  constructor() {
    this.objects = new Set()
    this.roots = [] // 根对象(全局变量、栈上的变量)
  }
  
  // 标记阶段
  mark() {
    const marked = new Set()
    const queue = [...this.roots]
    
    while (queue.length > 0) {
      const obj = queue.shift()
      
      if (marked.has(obj)) continue
      
      marked.add(obj)
      
      // 标记引用的对象
      for (const ref of obj.references || []) {
        queue.push(ref)
      }
    }
    
    return marked
  }
  
  // 清除阶段
  sweep(marked) {
    for (const obj of this.objects) {
      if (!marked.has(obj)) {
        // 未标记的对象,回收
        this.objects.delete(obj)
      }
    }
  }
  
  // 执行垃圾回收
  collect() {
    const marked = this.mark()
    this.sweep(marked)
  }
}

// 3. 标记-整理(Mark-Compact)
// 标记后,将存活对象移动到内存的一端,消除碎片
class MarkCompactGC extends MarkSweepGC {
  compact(marked) {
    const survivors = []
    
    // 收集存活对象
    for (const obj of this.objects) {
      if (marked.has(obj)) {
        survivors.push(obj)
      }
    }
    
    // 重新分配内存地址
    let address = 0
    for (const obj of survivors) {
      obj.address = address
      address += obj.size
    }
    
    // 更新所有引用
    for (const obj of survivors) {
      for (const ref of obj.references || []) {
        // 更新引用地址
      }
    }
    
    this.objects = new Set(survivors)
  }
  
  collect() {
    const marked = this.mark()
    this.compact(marked)
  }
}

// 4. 分代垃圾回收(Generational GC)
// 将对象分为新生代和老生代,分别使用不同的回收策略
class GenerationalGC {
  constructor() {
    this.newSpace = {
      fromSpace: [],
      toSpace: []
    }
    this.oldSpace = []
    this.roots = []
  }
  
  // 新生代回收(Scavenge)
  scavenge() {
    const { fromSpace, toSpace } = this.newSpace
    
    // 标记-复制
    for (const obj of fromSpace) {
      if (this.isReachable(obj)) {
        // 复制到 To-Space
        toSpace.push(obj)
        obj.age++
        
        // 晋升到老生代
        if (obj.age > 2) {
          this.oldSpace.push(obj)
          toSpace.pop()
        }
      }
    }
    
    // 交换 From-Space 和 To-Space
    this.newSpace.fromSpace = toSpace
    this.newSpace.toSpace = []
  }
  
  // 老生代回收(Mark-Sweep-Compact)
  majorGC() {
    const marked = this.mark(this.roots)
    this.sweep(marked)
    this.compact(marked)
  }
  
  // 判断对象是否可达
  isReachable(obj) {
    const visited = new Set()
    const queue = [...this.roots]
    
    while (queue.length > 0) {
      const current = queue.shift()
      
      if (visited.has(current)) continue
      if (current === obj) return true
      
      visited.add(current)
      
      for (const ref of current.references || []) {
        queue.push(ref)
      }
    }
    
    return false
  }
}

// 5. 增量标记(Incremental Marking)
// 将标记过程分成多个小步骤,避免长时间暂停
class IncrementalMarkingGC {
  constructor() {
    this.objects = new Set()
    this.roots = []
    this.markQueue = []
    this.marked = new Set()
  }
  
  // 开始增量标记
  startMarking() {
    this.markQueue = [...this.roots]
    this.marked.clear()
  }
  
  // 执行一步标记
  markStep(budget = 100) {
    let processed = 0
    
    while (this.markQueue.length > 0 && processed < budget) {
      const obj = this.markQueue.shift()
      
      if (this.marked.has(obj)) continue
      
      this.marked.add(obj)
      processed++
      
      // 将引用的对象加入队列
      for (const ref of obj.references || []) {
        this.markQueue.push(ref)
      }
    }
    
    return this.markQueue.length === 0
  }
  
  // 完成标记
  finishMarking() {
    while (!this.markStep()) {
      // 继续标记
    }
    
    // 清除未标记的对象
    for (const obj of this.objects) {
      if (!this.marked.has(obj)) {
        this.objects.delete(obj)
      }
    }
  }
}

2. 内存泄漏排查

javascript
// 1. 常见内存泄漏场景

// ❌ 场景1:意外的全局变量
function createLeak() {
  leak = 'I am a global variable' // 忘记使用 var/let/const
}

// ✅ 修复:使用严格模式
'use strict'
function noLeak() {
  const notLeak = 'I am a local variable'
}

// ❌ 场景2:被遗忘的定时器
const data = new Array(1000000).fill('data')

setInterval(() => {
  console.log(data) // data 永远不会被回收
}, 1000)

// ✅ 修复:清除定时器
const timerId = setInterval(() => {
  console.log(data)
}, 1000)

// 不再需要时清除
clearInterval(timerId)

// ❌ 场景3:闭包引用
function createClosure() {
  const largeData = new Array(1000000).fill('data')
  
  return function() {
    console.log(largeData[0]) // 闭包持有 largeData 的引用
  }
}

const closure = createClosure() // largeData 无法被回收

// ✅ 修复:手动释放引用
function createClosure() {
  let largeData = new Array(1000000).fill('data')
  
  return {
    getData() {
      return largeData[0]
    },
    dispose() {
      largeData = null // 释放引用
    }
  }
}

const closure = createClosure()
// 使用完毕后
closure.dispose()

// ❌ 场景4:DOM 引用
const elements = []

function addElement() {
  const div = document.createElement('div')
  document.body.appendChild(div)
  elements.push(div) // 保存 DOM 引用
}

// 即使从 DOM 中移除,elements 仍然持有引用
document.body.removeChild(elements[0])

// ✅ 修复:使用 WeakMap
const elements = new WeakMap()

function addElement() {
  const div = document.createElement('div')
  document.body.appendChild(div)
  elements.set(div, { data: 'some data' })
}

// DOM 移除后,WeakMap 中的引用会自动清除

// ❌ 场景5:事件监听器
const button = document.querySelector('button')

button.addEventListener('click', function handler() {
  console.log('clicked')
  // handler 持有外部作用域的引用
})

// ✅ 修复:移除事件监听器
const button = document.querySelector('button')

function handler() {
  console.log('clicked')
}

button.addEventListener('click', handler)

// 不再需要时移除
button.removeEventListener('click', handler)

// 2. 内存泄漏检测工具

// 使用 Chrome DevTools Memory Profiler
// 1. 打开 DevTools → Memory
// 2. 选择 "Heap snapshot"
// 3. 点击 "Take snapshot"
// 4. 执行可能导致内存泄漏的操作
// 5. 再次 "Take snapshot"
// 6. 对比两次快照,查找增长的对象

// 使用 Performance Monitor
// 1. 打开 DevTools → More tools → Performance monitor
// 2. 观察 "JS heap size" 指标
// 3. 如果持续增长,可能存在内存泄漏

// 3. 内存泄漏监控代码
class MemoryLeakDetector {
  constructor() {
    this.snapshots = []
    this.threshold = 50 * 1024 * 1024 // 50MB
  }
  
  // 记录内存快照
  takeSnapshot() {
    if (performance.memory) {
      const snapshot = {
        timestamp: Date.now(),
        usedJSHeapSize: performance.memory.usedJSHeapSize,
        totalJSHeapSize: performance.memory.totalJSHeapSize,
        jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
      }
      
      this.snapshots.push(snapshot)
      
      // 检测内存泄漏
      this.detect()
    }
  }
  
  // 检测内存泄漏
  detect() {
    if (this.snapshots.length < 2) return
    
    const latest = this.snapshots[this.snapshots.length - 1]
    const previous = this.snapshots[this.snapshots.length - 2]
    
    const growth = latest.usedJSHeapSize - previous.usedJSHeapSize
    
    if (growth > this.threshold) {
      console.warn('检测到内存泄漏:', {
        growth: `${(growth / 1024 / 1024).toFixed(2)} MB`,
        current: `${(latest.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
        limit: `${(latest.jsHeapSizeLimit / 1024 / 1024).toFixed(2)} MB`
      })
      
      // 上报到监控系统
      this.report(growth)
    }
  }
  
  // 上报内存泄漏
  report(growth) {
    // 发送到监控系统
    fetch('/api/memory-leak', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        growth,
        userAgent: navigator.userAgent,
        url: location.href,
        timestamp: Date.now()
      })
    })
  }
  
  // 定期检测
  startMonitoring(interval = 60000) {
    setInterval(() => {
      this.takeSnapshot()
    }, interval)
  }
}

// 使用示例
const detector = new MemoryLeakDetector()
detector.startMonitoring() // 每分钟检测一次

事件循环深入

1. 事件循环机制

javascript
// 事件循环(Event Loop)
// JavaScript 是单线程的,通过事件循环实现异步

// 事件循环模型
const eventLoopModel = {
  调用栈: 'Call Stack - 执行同步代码',
  微任务队列: 'Microtask Queue - Promise、MutationObserver',
  宏任务队列: 'Macrotask Queue - setTimeout、setInterval、I/O',
  执行顺序: '同步代码 → 微任务 → 宏任务 → 微任务 → ...'
}

// 1. 宏任务(Macrotask)
// - setTimeout
// - setInterval
// - setImmediate(Node.js)
// - I/O
// - UI 渲染

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

// 示例1:基础事件循环
console.log('1') // 同步代码

setTimeout(() => {
  console.log('2') // 宏任务
}, 0)

Promise.resolve().then(() => {
  console.log('3') // 微任务
})

console.log('4') // 同步代码

// 输出顺序:1 → 4 → 3 → 2

// 示例2:复杂事件循环
console.log('start')

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

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

console.log('end')

// 输出顺序:
// start → end → promise2 → setTimeout1 → promise1 → setTimeout2

// 示例3:async/await
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

// 3. 事件循环实现
class EventLoop {
  constructor() {
    this.callStack = []
    this.microtaskQueue = []
    this.macrotaskQueue = []
    this.isRunning = false
  }
  
  // 执行同步代码
  execute(fn) {
    this.callStack.push(fn)
    fn()
    this.callStack.pop()
  }
  
  // 添加微任务
  queueMicrotask(fn) {
    this.microtaskQueue.push(fn)
  }
  
  // 添加宏任务
  queueMacrotask(fn) {
    this.macrotaskQueue.push(fn)
  }
  
  // 运行事件循环
  run() {
    if (this.isRunning) return
    
    this.isRunning = true
    
    while (this.macrotaskQueue.length > 0 || this.microtaskQueue.length > 0) {
      // 1. 执行一个宏任务
      if (this.macrotaskQueue.length > 0) {
        const task = this.macrotaskQueue.shift()
        this.execute(task)
      }
      
      // 2. 执行所有微任务
      while (this.microtaskQueue.length > 0) {
        const task = this.microtaskQueue.shift()
        this.execute(task)
      }
      
      // 3. 渲染(如果需要)
      this.render()
    }
    
    this.isRunning = false
  }
  
  // 渲染
  render() {
    // 更新 DOM
    // 执行 requestAnimationFrame 回调
  }
}

// 4. requestAnimationFrame
// 在浏览器下一次重绘之前执行
function animate() {
  // 更新动画
  requestAnimationFrame(animate)
}

requestAnimationFrame(animate)

// 5. requestIdleCallback
// 在浏览器空闲时执行
requestIdleCallback((deadline) => {
  // deadline.timeRemaining() 返回剩余时间
  while (deadline.timeRemaining() > 0) {
    // 执行低优先级任务
  }
})

2. Node.js 事件循环

javascript
// Node.js 事件循环有 6 个阶段

const nodeEventLoop = {
  阶段1: 'timers - 执行 setTimeout/setInterval 回调',
  阶段2: 'pending callbacks - 执行延迟到下一个循环迭代的 I/O 回调',
  阶段3: 'idle, prepare - 仅内部使用',
  阶段4: 'poll - 检索新的 I/O 事件',
  阶段5: 'check - 执行 setImmediate 回调',
  阶段6: 'close callbacks - 执行关闭回调'
}

// process.nextTick vs setImmediate
// process.nextTick:在当前阶段结束后立即执行
// setImmediate:在 check 阶段执行

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

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

// 输出:nextTick → setImmediate

// 示例:I/O 操作
const fs = require('fs')

fs.readFile('file.txt', () => {
  console.log('readFile')
  
  setTimeout(() => {
    console.log('setTimeout')
  }, 0)
  
  setImmediate(() => {
    console.log('setImmediate')
  })
})

// 输出:readFile → setImmediate → setTimeout
// 在 I/O 回调中,setImmediate 总是先于 setTimeout 执行

浏览器缓存策略

1. HTTP 缓存

javascript
// 1. 强缓存(Strong Cache)
// 不需要向服务器发送请求,直接从缓存读取

// 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. 协商缓存(Negotiation Cache)
// 需要向服务器验证缓存是否有效

// 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(url, response, cacheControl) {
    const { maxAge, noCache, noStore } = this.parseCacheControl(cacheControl)
    
    if (noStore) {
      // 不缓存
      return
    }
    
    this.cache.set(url, {
      response,
      timestamp: Date.now(),
      maxAge,
      noCache,
      etag: response.headers.get('ETag'),
      lastModified: response.headers.get('Last-Modified')
    })
  }
  
  // 获取缓存
  async get(url) {
    const cached = this.cache.get(url)
    
    if (!cached) {
      // 缓存未命中
      return null
    }
    
    const age = Date.now() - cached.timestamp
    
    // 检查是否过期
    if (cached.maxAge && age > cached.maxAge * 1000) {
      // 缓存过期
      if (cached.noCache || cached.etag || cached.lastModified) {
        // 需要验证
        return this.validate(url, cached)
      } else {
        // 删除缓存
        this.cache.delete(url)
        return null
      }
    }
    
    // 缓存有效
    return cached.response
  }
  
  // 验证缓存
  async validate(url, cached) {
    const headers = {}
    
    if (cached.etag) {
      headers['If-None-Match'] = cached.etag
    }
    
    if (cached.lastModified) {
      headers['If-Modified-Since'] = cached.lastModified
    }
    
    const response = await fetch(url, { headers })
    
    if (response.status === 304) {
      // 缓存仍然有效
      cached.timestamp = Date.now()
      return cached.response
    } else {
      // 缓存失效,更新缓存
      this.set(url, response, response.headers.get('Cache-Control'))
      return response
    }
  }
  
  // 解析 Cache-Control
  parseCacheControl(cacheControl) {
    if (!cacheControl) return {}
    
    const directives = cacheControl.split(',').map(d => d.trim())
    const result = {}
    
    for (const directive of directives) {
      if (directive === 'no-cache') {
        result.noCache = true
      } else if (directive === 'no-store') {
        result.noStore = true
      } else if (directive.startsWith('max-age=')) {
        result.maxAge = parseInt(directive.split('=')[1])
      }
    }
    
    return result
  }
}

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

async function fetchWithCache(url) {
  // 尝试从缓存获取
  let response = await cacheStrategy.get(url)
  
  if (!response) {
    // 缓存未命中,发起请求
    response = await fetch(url)
    
    // 缓存响应
    const cacheControl = response.headers.get('Cache-Control')
    cacheStrategy.set(url, response, cacheControl)
  }
  
  return response
}

2. Service Worker 缓存

javascript
// Service Worker 提供更灵活的缓存控制

// sw.js
const CACHE_NAME = 'my-cache-v1'

// 安装时缓存静态资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/script.js',
        '/image.jpg'
      ])
    })
  )
})

// 拦截请求
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 缓存命中
      if (response) {
        return response
      }
      
      // 缓存未命中,发起网络请求
      return fetch(event.request).then((response) => {
        // 缓存响应
        return caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, response.clone())
          return response
        })
      })
    })
  )
})

// 缓存策略

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

// 2. Network First(网络优先)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request).catch(() => {
      return caches.match(event.request)
    })
  )
})

// 3. Stale While Revalidate(过期重新验证)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open(CACHE_NAME).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
      })
    })
  )
})

3. 浏览器存储

javascript
// 1. localStorage
// 持久化存储,容量约 5-10MB
localStorage.setItem('key', 'value')
const value = localStorage.getItem('key')
localStorage.removeItem('key')
localStorage.clear()

// 2. sessionStorage
// 会话存储,关闭标签页后清除
sessionStorage.setItem('key', 'value')
const value = sessionStorage.getItem('key')

// 3. IndexedDB
// 大容量存储,支持事务
const request = indexedDB.open('myDatabase', 1)

request.onsuccess = (event) => {
  const db = event.target.result
  
  // 添加数据
  const transaction = db.transaction(['users'], 'readwrite')
  const objectStore = transaction.objectStore('users')
  objectStore.add({ id: 1, name: '张三' })
}

// 4. Cache API
// Service Worker 缓存
caches.open('my-cache').then((cache) => {
  cache.add('/api/data')
})

// 5. Cookie
// 小容量存储,容量约 4KB
document.cookie = 'key=value; max-age=3600; path=/'

// 6. 存储对比
const storageComparison = {
  localStorage: {
    容量: '5-10MB',
    持久化: '是',
    作用域: '同源',
    API: '同步'
  },
  sessionStorage: {
    容量: '5-10MB',
    持久化: '否(会话)',
    作用域: '同源+同标签页',
    API: '同步'
  },
  IndexedDB: {
    容量: '无限制(受磁盘限制)',
    持久化: '是',
    作用域: '同源',
    API: '异步'
  },
  Cookie: {
    容量: '4KB',
    持久化: '可选',
    作用域: '同源',
    API: '同步'
  }
}

浏览器安全机制

1. 同源策略(Same-Origin Policy)

javascript
// 同源:协议、域名、端口都相同

// 同源示例
const origin1 = 'https://example.com:443/page1'
const origin2 = 'https://example.com:443/page2'
// 同源 ✅

// 不同源示例
const origin3 = 'http://example.com/page'  // 协议不同 ❌
const origin4 = 'https://api.example.com/page' // 域名不同 ❌
const origin5 = 'https://example.com:8080/page' // 端口不同 ❌

// 同源策略限制
// 1. 无法读取非同源的 Cookie、LocalStorage、IndexedDB
// 2. 无法操作非同源的 DOM
// 3. 无法发送非同源的 AJAX 请求(会被浏览器拦截)

// 跨域解决方案

// 1. CORS(Cross-Origin Resource Sharing)
// 服务端设置响应头
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

// 前端发送请求
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  credentials: 'include' // 携带 Cookie
})

// 2. JSONP(只支持 GET 请求)
function jsonp(url, callback) {
  const script = document.createElement('script')
  const callbackName = 'jsonp_' + Date.now()
  
  window[callbackName] = (data) => {
    callback(data)
    document.body.removeChild(script)
    delete window[callbackName]
  }
  
  script.src = `${url}?callback=${callbackName}`
  document.body.appendChild(script)
}

// 使用示例
jsonp('https://api.example.com/data', (data) => {
  console.log(data)
})

// 3. 代理服务器
// 前端请求同源的代理服务器
fetch('/api/data')

// 代理服务器转发请求到目标服务器
// Nginx 配置
location /api/ {
  proxy_pass https://api.example.com/;
}

// 4. postMessage(跨窗口通信)
// 父窗口
const iframe = document.querySelector('iframe')
iframe.contentWindow.postMessage('Hello', 'https://example.com')

// 子窗口
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://parent.com') return
  
  console.log(event.data) // 'Hello'
  
  // 回复消息
  event.source.postMessage('Hi', event.origin)
})

2. 内容安全策略(CSP)

javascript
// CSP 防止 XSS 攻击

// 设置 CSP 响应头
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'

// 或使用 meta 标签
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

// CSP 指令
const cspDirectives = {
  'default-src': '默认策略',
  'script-src': '脚本来源',
  'style-src': '样式来源',
  'img-src': '图片来源',
  'font-src': '字体来源',
  'connect-src': 'AJAX、WebSocket 来源',
  'frame-src': 'iframe 来源',
  'media-src': '音视频来源'
}

// CSP 值
const cspValues = {
  'self': '同源',
  'none': '禁止',
  'unsafe-inline': '允许内联脚本/样式',
  'unsafe-eval': '允许 eval()',
  'https:': '只允许 HTTPS',
  'https://example.com': '指定域名'
}

// 示例:严格的 CSP
Content-Security-Policy: 
  default-src 'none';
  script-src 'self' https://cdn.example.com;
  style-src 'self';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self'

// 违规报告
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

// 接收违规报告
app.post('/csp-report', (req, res) => {
  console.log('CSP 违规:', req.body)
  res.status(204).end()
})

3. XSS 防护

javascript
// XSS(Cross-Site Scripting)跨站脚本攻击

// 1. 反射型 XSS
// 攻击代码通过 URL 参数传入
// https://example.com/search?q=<script>alert('XSS')</script>

// 防护:转义用户输入
function escapeHTML(str) {
  const div = document.createElement('div')
  div.textContent = str
  return div.innerHTML
}

// 或使用库
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(dirty)

// 2. 存储型 XSS
// 攻击代码存储在数据库中
// 用户评论:<script>alert('XSS')</script>

// 防护:服务端过滤
function sanitizeInput(input) {
  // 移除危险标签
  return input.replace(/<script[^>]*>.*?<\/script>/gi, '')
              .replace(/<iframe[^>]*>.*?<\/iframe>/gi, '')
              .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
}

// 3. DOM 型 XSS
// 攻击代码通过 DOM 操作注入
document.getElementById('output').innerHTML = userInput // 危险 ❌

// 防护:使用安全的 API
document.getElementById('output').textContent = userInput // 安全 ✅

// 4. XSS 防护最佳实践
const xssProtection = {
  输入验证: '验证用户输入的格式和内容',
  输出转义: '转义 HTML 特殊字符',
  CSP: '设置内容安全策略',
  HttpOnly: 'Cookie 设置 HttpOnly 标志',
  X_XSS_Protection: '启用浏览器 XSS 过滤器'
}

// 设置 HttpOnly Cookie
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict

// 启用 XSS 过滤器
X-XSS-Protection: 1; mode=block

4. CSRF 防护

javascript
// CSRF(Cross-Site Request Forgery)跨站请求伪造

// 攻击示例
// 恶意网站包含:
<img src="https://bank.com/transfer?to=attacker&amount=1000">

// 防护方案

// 1. CSRF Token
// 服务端生成 Token
const csrfToken = generateToken()
res.cookie('csrfToken', csrfToken)

// 前端发送请求时携带 Token
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookie('csrfToken')
  },
  body: JSON.stringify({ to: 'user', amount: 100 })
})

// 服务端验证 Token
app.post('/api/transfer', (req, res) => {
  const token = req.headers['x-csrf-token']
  const cookieToken = req.cookies.csrfToken
  
  if (token !== cookieToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' })
  }
  
  // 处理请求
})

// 2. SameSite Cookie
Set-Cookie: sessionId=abc123; SameSite=Strict

// SameSite 值
const sameSiteValues = {
  Strict: '完全禁止第三方 Cookie',
  Lax: '部分允许第三方 Cookie(GET 请求)',
  None: '允许第三方 Cookie(需要 Secure)'
}

// 3. 验证 Referer
app.post('/api/transfer', (req, res) => {
  const referer = req.headers.referer
  
  if (!referer || !referer.startsWith('https://bank.com')) {
    return res.status(403).json({ error: 'Invalid referer' })
  }
  
  // 处理请求
})

// 4. 双重 Cookie 验证
// 服务端设置 Cookie
res.cookie('csrfToken', token)

// 前端读取 Cookie 并作为请求参数
fetch('/api/transfer', {
  method: 'POST',
  body: JSON.stringify({
    csrfToken: getCookie('csrfToken'),
    to: 'user',
    amount: 100
  })
})

// 服务端验证
app.post('/api/transfer', (req, res) => {
  const cookieToken = req.cookies.csrfToken
  const bodyToken = req.body.csrfToken
  
  if (cookieToken !== bodyToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' })
  }
  
  // 处理请求
})

性能优化最佳实践

1. 资源加载优化

javascript
// 1. 预加载(Preload)
// 提前加载关键资源
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="font.woff2" as="font" crossorigin>
<link rel="preload" href="hero.jpg" as="image">

// 2. 预连接(Preconnect)
// 提前建立连接
<link rel="preconnect" href="https://api.example.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

// 3. DNS 预解析(DNS Prefetch)
// 提前解析域名
<link rel="dns-prefetch" href="https://api.example.com">

// 4. 预取(Prefetch)
// 低优先级加载下一页资源
<link rel="prefetch" href="next-page.html">
<link rel="prefetch" href="next-page.js">

// 5. 预渲染(Prerender)
// 提前渲染整个页面
<link rel="prerender" href="next-page.html">

// 6. 模块预加载(Module Preload)
// 预加载 ES 模块
<link rel="modulepreload" href="app.js">

// 7. 动态导入
// 按需加载模块
const module = await import('./module.js')

// 8. 图片懒加载
// 使用 Intersection Observer
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      observer.unobserve(img)
    }
  })
})

document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img)
})

// 或使用原生懒加载
<img src="image.jpg" loading="lazy">

// 9. 资源优先级
// 使用 fetchpriority 属性
<img src="hero.jpg" fetchpriority="high">
<script src="analytics.js" fetchpriority="low"></script>

2. 代码优化

javascript
// 1. 代码分割
// 使用动态导入
const HomePage = () => import('./pages/Home.vue')
const AboutPage = () => import('./pages/About.vue')

// 2. Tree Shaking
// 移除未使用的代码
// 使用 ES Modules
import { used } from './utils' // ✅ 只打包 used
const { used, unused } = require('./utils') // ❌ 打包所有

// 3. 压缩代码
// 使用 Terser 压缩 JavaScript
// 使用 cssnano 压缩 CSS

// 4. 使用 CDN
// 加载第三方库
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>

// 5. 启用 Gzip/Brotli 压缩
// Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;

// 6. 使用 Web Workers
// 将计算密集型任务移到 Worker
const worker = new Worker('worker.js')
worker.postMessage({ data: largeArray })
worker.onmessage = (e) => {
  console.log('结果:', e.data)
}

// 7. 使用 WebAssembly
// 执行高性能计算
const wasmModule = await WebAssembly.instantiateStreaming(
  fetch('module.wasm')
)
const result = wasmModule.instance.exports.compute(data)

3. 渲染优化

javascript
// 1. 避免强制同步布局
// ❌ 强制同步布局
const height = element.offsetHeight // 读取布局
element.style.height = height + 10 + 'px' // 修改布局
const width = element.offsetWidth // 再次读取,触发重排

// ✅ 批量读取和修改
const height = element.offsetHeight
const width = element.offsetWidth
element.style.height = height + 10 + 'px'
element.style.width = width + 10 + 'px'

// 2. 使用 requestAnimationFrame
// ❌ 直接修改样式
setInterval(() => {
  element.style.left = element.offsetLeft + 1 + 'px'
}, 16)

// ✅ 使用 rAF
function animate() {
  element.style.left = element.offsetLeft + 1 + 'px'
  requestAnimationFrame(animate)
}
requestAnimationFrame(animate)

// 3. 使用 transform 代替 top/left
// ❌ 触发重排
element.style.left = '100px'

// ✅ 只触发合成
element.style.transform = 'translateX(100px)'

// 4. 使用 will-change
// 提示浏览器元素将要变化
element.style.willChange = 'transform'

// 使用后移除
element.addEventListener('animationend', () => {
  element.style.willChange = 'auto'
})

// 5. 虚拟滚动
// 只渲染可见区域的元素
class VirtualScroll {
  constructor(container, items, itemHeight) {
    this.container = container
    this.items = items
    this.itemHeight = itemHeight
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight)
    
    this.render()
    this.container.addEventListener('scroll', () => this.render())
  }
  
  render() {
    const scrollTop = this.container.scrollTop
    const startIndex = Math.floor(scrollTop / this.itemHeight)
    const endIndex = startIndex + this.visibleCount
    
    const visibleItems = this.items.slice(startIndex, endIndex)
    
    this.container.innerHTML = visibleItems.map((item, index) => `
      <div style="position: absolute; top: ${(startIndex + index) * this.itemHeight}px; height: ${this.itemHeight}px;">
        ${item}
      </div>
    `).join('')
    
    this.container.style.height = this.items.length * this.itemHeight + 'px'
  }
}

// 6. 使用 CSS Containment
// 限制浏览器的样式、布局和绘制范围
.item {
  contain: layout style paint;
}

// 7. 使用 content-visibility
// 跳过屏幕外元素的渲染
.item {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
}

面试要点

1. 高频面试题

javascript
// Q1:浏览器渲染流程
// 答案:
// 1. 解析 HTML → DOM 树
// 2. 解析 CSS → CSSOM 树
// 3. DOM + CSSOM → 渲染树
// 4. 布局(Layout)→ 计算位置和大小
// 5. 绘制(Paint)→ 绘制像素
// 6. 合成(Composite)→ 合成图层

// Q2:重排和重绘的区别
// 答案:
// 重排(Reflow):元素的几何属性变化,需要重新计算布局
// 重绘(Repaint):元素的外观变化,不影响布局
// 重排一定会导致重绘,重绘不一定导致重排

// Q3:如何优化重排和重绘
// 答案:
// 1. 批量修改 DOM
// 2. 使用 transform 代替 top/left
// 3. 使用 will-change 提示浏览器
// 4. 避免频繁读取布局信息
// 5. 使用 DocumentFragment
// 6. 使用 CSS3 动画代替 JavaScript 动画

// Q4:V8 引擎的优化技巧
// 答案:
// 1. 保持对象形状一致(隐藏类)
// 2. 避免动态添加/删除属性
// 3. 使用单态函数
// 4. 避免稀疏数组
// 5. 使用类型化数组
// 6. 避免在循环中创建函数

// Q5:垃圾回收机制
// 答案:
// 1. 新生代:Scavenge 算法(标记-复制)
// 2. 老生代:Mark-Sweep-Compact 算法
// 3. 增量标记:避免长时间暂停
// 4. 并发标记:在主线程执行的同时进行标记

// Q6:事件循环
// 答案:
// 1. 执行同步代码
// 2. 执行所有微任务
// 3. 执行一个宏任务
// 4. 执行所有微任务
// 5. 渲染(如果需要)
// 6. 重复步骤 3-5

// Q7:浏览器缓存策略
// 答案:
// 1. 强缓存:Cache-Control、Expires
// 2. 协商缓存:ETag、Last-Modified
// 3. Service Worker 缓存
// 4. 浏览器存储:localStorage、sessionStorage、IndexedDB

// Q8:跨域解决方案
// 答案:
// 1. CORS
// 2. JSONP
// 3. 代理服务器
// 4. postMessage
// 5. WebSocket

// Q9:XSS 防护
// 答案:
// 1. 输入验证
// 2. 输出转义
// 3. CSP
// 4. HttpOnly Cookie
// 5. X-XSS-Protection

// Q10:CSRF 防护
// 答案:
// 1. CSRF Token
// 2. SameSite Cookie
// 3. 验证 Referer
// 4. 双重 Cookie 验证

2. 面试技巧

javascript
// 1. 回答框架(STAR 法则)
const starMethod = {
  Situation: '描述场景',
  Task: '说明任务',
  Action: '采取的行动',
  Result: '取得的结果'
}

// 示例:优化页面性能
// Situation:页面加载时间超过 5 秒,用户体验差
// Task:将加载时间优化到 2 秒以内
// Action:
// 1. 使用 Lighthouse 分析性能瓶颈
// 2. 压缩图片,使用 WebP 格式
// 3. 代码分割,按需加载
// 4. 启用 Gzip 压缩
// 5. 使用 CDN 加速
// Result:加载时间从 5 秒降低到 1.8 秒,用户满意度提升 30%

// 2. 加分项
const bonusPoints = [
  '了解浏览器内部实现原理',
  '能够使用 Chrome DevTools 分析性能',
  '有实际的性能优化经验',
  '了解最新的 Web 标准和 API',
  '能够解释复杂概念(如事件循环、垃圾回收)',
  '有开源项目或技术博客'
]

// 3. 高频问题
const frequentQuestions = [
  '浏览器渲染流程',
  '重排和重绘',
  'V8 引擎优化',
  '垃圾回收机制',
  '事件循环',
  '浏览器缓存',
  '跨域解决方案',
  'XSS 和 CSRF 防护',
  '性能优化',
  'Web Vitals'
]

参考资料

官方资源

学习资源

性能工具

学习路线

  1. 初级:了解浏览器渲染流程、事件循环、缓存机制
  2. 中级:深入 V8 引擎、垃圾回收、性能优化
  3. 高级:浏览器安全、Service Worker、WebAssembly

💡 提示:浏览器原理是前端面试的重点,建议结合实际项目经验,深入理解每个概念的原理和应用场景。

基于 VitePress 构建