发布于

Web性能优化全攻略:从加载速度到用户体验的全面提升

作者

Web性能优化全攻略:从加载速度到用户体验的全面提升

Web性能优化是现代前端开发的核心技能之一。本文将系统性地介绍各种性能优化技术和最佳实践。

性能指标与监控

Core Web Vitals核心指标

// 性能监控工具类
class PerformanceMonitor {
  constructor() {
    this.metrics = {}
    this.observers = new Map()
    this.init()
  }

  init() {
    // 监控LCP (Largest Contentful Paint)
    this.observeLCP()
    
    // 监控FID (First Input Delay)
    this.observeFID()
    
    // 监控CLS (Cumulative Layout Shift)
    this.observeCLS()
    
    // 监控FCP (First Contentful Paint)
    this.observeFCP()
    
    // 监控TTFB (Time to First Byte)
    this.observeTTFB()
  }

  // 监控最大内容绘制
  observeLCP() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries()
        const lastEntry = entries[entries.length - 1]
        
        this.metrics.lcp = {
          value: lastEntry.startTime,
          element: lastEntry.element,
          timestamp: Date.now()
        }
        
        this.reportMetric('LCP', lastEntry.startTime)
      })
      
      observer.observe({ entryTypes: ['largest-contentful-paint'] })
      this.observers.set('lcp', observer)
    }
  }

  // 监控首次输入延迟
  observeFID() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries()
        entries.forEach((entry) => {
          this.metrics.fid = {
            value: entry.processingStart - entry.startTime,
            timestamp: Date.now()
          }
          
          this.reportMetric('FID', entry.processingStart - entry.startTime)
        })
      })
      
      observer.observe({ entryTypes: ['first-input'] })
      this.observers.set('fid', observer)
    }
  }

  // 监控累积布局偏移
  observeCLS() {
    if ('PerformanceObserver' in window) {
      let clsValue = 0
      let sessionValue = 0
      let sessionEntries = []
      
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries()
        
        entries.forEach((entry) => {
          if (!entry.hadRecentInput) {
            const firstSessionEntry = sessionEntries[0]
            const lastSessionEntry = sessionEntries[sessionEntries.length - 1]
            
            if (sessionValue && 
                entry.startTime - lastSessionEntry.startTime < 1000 &&
                entry.startTime - firstSessionEntry.startTime < 5000) {
              sessionValue += entry.value
              sessionEntries.push(entry)
            } else {
              sessionValue = entry.value
              sessionEntries = [entry]
            }
            
            if (sessionValue > clsValue) {
              clsValue = sessionValue
              this.metrics.cls = {
                value: clsValue,
                entries: [...sessionEntries],
                timestamp: Date.now()
              }
              
              this.reportMetric('CLS', clsValue)
            }
          }
        })
      })
      
      observer.observe({ entryTypes: ['layout-shift'] })
      this.observers.set('cls', observer)
    }
  }

  // 监控首次内容绘制
  observeFCP() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries()
        entries.forEach((entry) => {
          if (entry.name === 'first-contentful-paint') {
            this.metrics.fcp = {
              value: entry.startTime,
              timestamp: Date.now()
            }
            
            this.reportMetric('FCP', entry.startTime)
          }
        })
      })
      
      observer.observe({ entryTypes: ['paint'] })
      this.observers.set('fcp', observer)
    }
  }

  // 监控首字节时间
  observeTTFB() {
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries()
        entries.forEach((entry) => {
          if (entry.entryType === 'navigation') {
            const ttfb = entry.responseStart - entry.requestStart
            
            this.metrics.ttfb = {
              value: ttfb,
              timestamp: Date.now()
            }
            
            this.reportMetric('TTFB', ttfb)
          }
        })
      })
      
      observer.observe({ entryTypes: ['navigation'] })
      this.observers.set('ttfb', observer)
    }
  }

  // 获取资源加载性能
  getResourceTiming() {
    const resources = performance.getEntriesByType('resource')
    
    return resources.map(resource => ({
      name: resource.name,
      type: this.getResourceType(resource.name),
      size: resource.transferSize,
      duration: resource.duration,
      startTime: resource.startTime,
      dns: resource.domainLookupEnd - resource.domainLookupStart,
      tcp: resource.connectEnd - resource.connectStart,
      ssl: resource.secureConnectionStart > 0 ? 
           resource.connectEnd - resource.secureConnectionStart : 0,
      ttfb: resource.responseStart - resource.requestStart,
      download: resource.responseEnd - resource.responseStart
    }))
  }

  // 获取资源类型
  getResourceType(url) {
    const extension = url.split('.').pop()?.toLowerCase()
    
    if (['js', 'mjs'].includes(extension)) return 'script'
    if (['css'].includes(extension)) return 'stylesheet'
    if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(extension)) return 'image'
    if (['woff', 'woff2', 'ttf', 'otf'].includes(extension)) return 'font'
    
    return 'other'
  }

  // 上报性能指标
  reportMetric(name, value) {
    // 发送到分析服务
    if (typeof gtag !== 'undefined') {
      gtag('event', name, {
        event_category: 'Web Vitals',
        value: Math.round(name === 'CLS' ? value * 1000 : value),
        non_interaction: true
      })
    }
    
    // 发送到自定义分析服务
    this.sendToAnalytics({
      metric: name,
      value: value,
      url: window.location.href,
      userAgent: navigator.userAgent,
      timestamp: Date.now()
    })
  }

  // 发送分析数据
  async sendToAnalytics(data) {
    try {
      await fetch('/api/analytics/performance', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      })
    } catch (error) {
      console.warn('Failed to send performance data:', error)
    }
  }

  // 获取所有指标
  getAllMetrics() {
    return {
      ...this.metrics,
      resources: this.getResourceTiming(),
      memory: this.getMemoryInfo(),
      connection: this.getConnectionInfo()
    }
  }

  // 获取内存信息
  getMemoryInfo() {
    if ('memory' in performance) {
      return {
        used: performance.memory.usedJSHeapSize,
        total: performance.memory.totalJSHeapSize,
        limit: performance.memory.jsHeapSizeLimit
      }
    }
    return null
  }

  // 获取网络连接信息
  getConnectionInfo() {
    if ('connection' in navigator) {
      const connection = navigator.connection
      return {
        effectiveType: connection.effectiveType,
        downlink: connection.downlink,
        rtt: connection.rtt,
        saveData: connection.saveData
      }
    }
    return null
  }

  // 清理观察器
  disconnect() {
    this.observers.forEach(observer => observer.disconnect())
    this.observers.clear()
  }
}

// 初始化性能监控
const performanceMonitor = new PerformanceMonitor()

// 页面卸载时发送最终数据
window.addEventListener('beforeunload', () => {
  const metrics = performanceMonitor.getAllMetrics()
  
  // 使用sendBeacon确保数据发送
  if ('sendBeacon' in navigator) {
    navigator.sendBeacon('/api/analytics/performance', JSON.stringify(metrics))
  }
})

性能预算管理

// 性能预算配置
const PERFORMANCE_BUDGET = {
  // 资源大小限制 (KB)
  resources: {
    javascript: 300,
    css: 100,
    images: 500,
    fonts: 100,
    total: 1000
  },
  
  // 性能指标限制 (ms)
  metrics: {
    fcp: 1500,
    lcp: 2500,
    fid: 100,
    cls: 0.1,
    ttfb: 600
  },
  
  // 网络请求限制
  requests: {
    total: 50,
    javascript: 10,
    css: 5,
    images: 20,
    fonts: 5
  }
}

// 性能预算检查器
class PerformanceBudgetChecker {
  constructor(budget = PERFORMANCE_BUDGET) {
    this.budget = budget
    this.violations = []
  }

  // 检查资源预算
  checkResourceBudget() {
    const resources = performance.getEntriesByType('resource')
    const resourceStats = this.analyzeResources(resources)
    
    Object.entries(this.budget.resources).forEach(([type, limit]) => {
      const actual = resourceStats.sizes[type] || 0
      const actualKB = Math.round(actual / 1024)
      
      if (actualKB > limit) {
        this.violations.push({
          type: 'resource_size',
          category: type,
          limit: limit,
          actual: actualKB,
          severity: this.getSeverity(actualKB, limit)
        })
      }
    })
    
    // 检查请求数量
    Object.entries(this.budget.requests).forEach(([type, limit]) => {
      const actual = resourceStats.counts[type] || 0
      
      if (actual > limit) {
        this.violations.push({
          type: 'request_count',
          category: type,
          limit: limit,
          actual: actual,
          severity: this.getSeverity(actual, limit)
        })
      }
    })
  }

  // 检查性能指标预算
  checkMetricsBudget(metrics) {
    Object.entries(this.budget.metrics).forEach(([metric, limit]) => {
      const actual = metrics[metric]?.value
      
      if (actual && actual > limit) {
        this.violations.push({
          type: 'performance_metric',
          category: metric,
          limit: limit,
          actual: Math.round(actual),
          severity: this.getSeverity(actual, limit)
        })
      }
    })
  }

  // 分析资源
  analyzeResources(resources) {
    const stats = {
      sizes: {},
      counts: {}
    }
    
    resources.forEach(resource => {
      const type = this.getResourceType(resource.name)
      const size = resource.transferSize || 0
      
      stats.sizes[type] = (stats.sizes[type] || 0) + size
      stats.counts[type] = (stats.counts[type] || 0) + 1
    })
    
    // 计算总计
    stats.sizes.total = Object.values(stats.sizes).reduce((sum, size) => sum + size, 0)
    stats.counts.total = Object.values(stats.counts).reduce((sum, count) => sum + count, 0)
    
    return stats
  }

  // 获取资源类型
  getResourceType(url) {
    const extension = url.split('.').pop()?.toLowerCase()
    
    if (['js', 'mjs'].includes(extension)) return 'javascript'
    if (['css'].includes(extension)) return 'css'
    if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(extension)) return 'images'
    if (['woff', 'woff2', 'ttf', 'otf'].includes(extension)) return 'fonts'
    
    return 'other'
  }

  // 获取违规严重程度
  getSeverity(actual, limit) {
    const ratio = actual / limit
    
    if (ratio >= 2) return 'critical'
    if (ratio >= 1.5) return 'high'
    if (ratio >= 1.2) return 'medium'
    return 'low'
  }

  // 生成报告
  generateReport() {
    const report = {
      timestamp: Date.now(),
      url: window.location.href,
      violations: this.violations,
      summary: {
        total: this.violations.length,
        critical: this.violations.filter(v => v.severity === 'critical').length,
        high: this.violations.filter(v => v.severity === 'high').length,
        medium: this.violations.filter(v => v.severity === 'medium').length,
        low: this.violations.filter(v => v.severity === 'low').length
      }
    }
    
    return report
  }

  // 发送报告
  async sendReport() {
    const report = this.generateReport()
    
    try {
      await fetch('/api/performance/budget-report', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(report)
      })
    } catch (error) {
      console.warn('Failed to send budget report:', error)
    }
  }
}

资源优化

图片优化策略

// 响应式图片组件
class ResponsiveImage {
  constructor(element, options = {}) {
    this.element = element
    this.options = {
      sizes: ['320w', '640w', '1024w', '1920w'],
      formats: ['webp', 'jpg'],
      quality: 80,
      lazy: true,
      placeholder: true,
      ...options
    }
    
    this.init()
  }

  init() {
    if (this.options.lazy) {
      this.setupLazyLoading()
    }
    
    if (this.options.placeholder) {
      this.setupPlaceholder()
    }
    
    this.setupResponsiveImages()
  }

  // 设置懒加载
  setupLazyLoading() {
    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            this.loadImage()
            observer.unobserve(entry.target)
          }
        })
      }, {
        rootMargin: '50px'
      })
      
      observer.observe(this.element)
    } else {
      // 降级处理
      this.loadImage()
    }
  }

  // 设置占位符
  setupPlaceholder() {
    const src = this.element.dataset.src
    if (!src) return
    
    // 生成低质量占位符
    const placeholderSrc = this.generatePlaceholder(src)
    this.element.src = placeholderSrc
    this.element.classList.add('image-placeholder')
  }

  // 生成占位符
  generatePlaceholder(src) {
    // 使用服务端生成的低质量图片
    const url = new URL(src)
    url.searchParams.set('w', '20')
    url.searchParams.set('q', '20')
    url.searchParams.set('blur', '5')
    return url.toString()
  }

  // 设置响应式图片
  setupResponsiveImages() {
    const src = this.element.dataset.src
    if (!src) return
    
    // 生成srcset
    const srcset = this.generateSrcSet(src)
    this.element.dataset.srcset = srcset
    
    // 设置sizes属性
    if (!this.element.sizes) {
      this.element.sizes = this.generateSizes()
    }
  }

  // 生成srcset
  generateSrcSet(src) {
    const srcsets = []
    
    this.options.formats.forEach(format => {
      this.options.sizes.forEach(size => {
        const width = parseInt(size)
        const url = this.buildImageUrl(src, { width, format })
        srcsets.push(`${url} ${size}`)
      })
    })
    
    return srcsets.join(', ')
  }

  // 生成sizes属性
  generateSizes() {
    return [
      '(max-width: 320px) 320px',
      '(max-width: 640px) 640px',
      '(max-width: 1024px) 1024px',
      '1920px'
    ].join(', ')
  }

  // 构建图片URL
  buildImageUrl(src, params) {
    const url = new URL(src)
    
    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.set(key, value)
    })
    
    url.searchParams.set('q', this.options.quality)
    
    return url.toString()
  }

  // 加载图片
  async loadImage() {
    const src = this.element.dataset.src
    const srcset = this.element.dataset.srcset
    
    if (!src) return
    
    try {
      // 预加载图片
      await this.preloadImage(src, srcset)
      
      // 更新图片源
      if (srcset) {
        this.element.srcset = srcset
      }
      this.element.src = src
      
      // 移除占位符样式
      this.element.classList.remove('image-placeholder')
      this.element.classList.add('image-loaded')
      
    } catch (error) {
      console.warn('Failed to load image:', error)
      this.element.classList.add('image-error')
    }
  }

  // 预加载图片
  preloadImage(src, srcset) {
    return new Promise((resolve, reject) => {
      const img = new Image()
      
      img.onload = resolve
      img.onerror = reject
      
      if (srcset) {
        img.srcset = srcset
      }
      img.src = src
    })
  }
}

// 图片优化工具
class ImageOptimizer {
  static async compressImage(file, options = {}) {
    const {
      maxWidth = 1920,
      maxHeight = 1080,
      quality = 0.8,
      format = 'jpeg'
    } = options
    
    return new Promise((resolve) => {
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const img = new Image()
      
      img.onload = () => {
        // 计算新尺寸
        const { width, height } = this.calculateDimensions(
          img.width, 
          img.height, 
          maxWidth, 
          maxHeight
        )
        
        canvas.width = width
        canvas.height = height
        
        // 绘制图片
        ctx.drawImage(img, 0, 0, width, height)
        
        // 转换为Blob
        canvas.toBlob(resolve, `image/${format}`, quality)
      }
      
      img.src = URL.createObjectURL(file)
    })
  }

  static calculateDimensions(width, height, maxWidth, maxHeight) {
    if (width <= maxWidth && height <= maxHeight) {
      return { width, height }
    }
    
    const aspectRatio = width / height
    
    if (width > height) {
      return {
        width: Math.min(width, maxWidth),
        height: Math.min(width, maxWidth) / aspectRatio
      }
    } else {
      return {
        width: Math.min(height, maxHeight) * aspectRatio,
        height: Math.min(height, maxHeight)
      }
    }
  }

  // 批量优化图片
  static async optimizeImages(files, options = {}) {
    const optimizedFiles = []
    
    for (const file of files) {
      if (file.type.startsWith('image/')) {
        const optimized = await this.compressImage(file, options)
        optimizedFiles.push(optimized)
      } else {
        optimizedFiles.push(file)
      }
    }
    
    return optimizedFiles
  }
}

// 自动初始化响应式图片
document.addEventListener('DOMContentLoaded', () => {
  const images = document.querySelectorAll('img[data-src]')
  images.forEach(img => new ResponsiveImage(img))
})

字体优化

/* 字体优化CSS */

/* 字体预加载 */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-font.woff2') format('woff2'),
       url('/fonts/custom-font.woff') format('woff');
  font-display: swap; /* 使用字体交换策略 */
  font-weight: 400;
  font-style: normal;
}

/* 字体子集化 */
@font-face {
  font-family: 'CustomFont-Latin';
  src: url('/fonts/custom-font-latin.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'CustomFont-Chinese';
  src: url('/fonts/custom-font-chinese.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+4E00-9FFF;
}

/* 字体回退策略 */
body {
  font-family: 'CustomFont', 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
}

/* 字体加载优化 */
.font-loading {
  font-family: Arial, sans-serif; /* 系统字体作为回退 */
}

.font-loaded {
  font-family: 'CustomFont', Arial, sans-serif;
}

/* 可变字体优化 */
@font-face {
  font-family: 'VariableFont';
  src: url('/fonts/variable-font.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

.variable-font {
  font-family: 'VariableFont', sans-serif;
  font-variation-settings: 'wght' 400, 'wdth' 100;
}

.variable-font-bold {
  font-variation-settings: 'wght' 700, 'wdth' 100;
}
// 字体加载优化
class FontLoader {
  constructor() {
    this.loadedFonts = new Set()
    this.fontObserver = null
    this.init()
  }

  init() {
    // 使用Font Loading API
    if ('fonts' in document) {
      this.useFontLoadingAPI()
    } else {
      // 降级到FontFaceObserver
      this.useFontFaceObserver()
    }
  }

  // 使用Font Loading API
  async useFontLoadingAPI() {
    const fonts = [
      { family: 'CustomFont', weight: '400' },
      { family: 'CustomFont', weight: '700' },
    ]

    try {
      // 预加载关键字体
      const fontPromises = fonts.map(font => 
        document.fonts.load(`${font.weight} 16px ${font.family}`)
      )

      await Promise.all(fontPromises)
      
      // 标记字体已加载
      document.documentElement.classList.add('fonts-loaded')
      
      // 存储到localStorage
      localStorage.setItem('fonts-loaded', Date.now().toString())
      
    } catch (error) {
      console.warn('Font loading failed:', error)
      this.handleFontLoadError()
    }
  }

  // 使用FontFaceObserver降级方案
  useFontFaceObserver() {
    // 这里需要引入FontFaceObserver库
    if (typeof FontFaceObserver !== 'undefined') {
      const font = new FontFaceObserver('CustomFont')
      
      font.load().then(() => {
        document.documentElement.classList.add('fonts-loaded')
        localStorage.setItem('fonts-loaded', Date.now().toString())
      }).catch(() => {
        this.handleFontLoadError()
      })
    }
  }

  // 处理字体加载错误
  handleFontLoadError() {
    document.documentElement.classList.add('fonts-failed')
    
    // 使用系统字体作为回退
    const style = document.createElement('style')
    style.textContent = `
      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
      }
    `
    document.head.appendChild(style)
  }

  // 检查字体缓存
  checkFontCache() {
    const cached = localStorage.getItem('fonts-loaded')
    if (cached) {
      const cacheTime = parseInt(cached)
      const now = Date.now()
      const oneWeek = 7 * 24 * 60 * 60 * 1000
      
      if (now - cacheTime < oneWeek) {
        document.documentElement.classList.add('fonts-cached')
        return true
      }
    }
    return false
  }

  // 预加载字体
  preloadFonts(fonts) {
    fonts.forEach(font => {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.as = 'font'
      link.type = 'font/woff2'
      link.crossOrigin = 'anonymous'
      link.href = font.url
      document.head.appendChild(link)
    })
  }
}

// 初始化字体加载器
const fontLoader = new FontLoader()

// 检查缓存
if (!fontLoader.checkFontCache()) {
  // 预加载关键字体
  fontLoader.preloadFonts([
    { url: '/fonts/custom-font-400.woff2' },
    { url: '/fonts/custom-font-700.woff2' }
  ])
}

代码分割与懒加载

动态导入优化

// 路由级代码分割
const routes = [
  {
    path: '/',
    component: () => import(/* webpackChunkName: "home" */ './pages/Home.vue')
  },
  {
    path: '/about',
    component: () => import(/* webpackChunkName: "about" */ './pages/About.vue')
  },
  {
    path: '/products',
    component: () => import(/* webpackChunkName: "products" */ './pages/Products.vue')
  }
]

// 组件级代码分割
const LazyComponent = React.lazy(() => 
  import(/* webpackChunkName: "lazy-component" */ './LazyComponent')
)

// 条件加载
const ConditionalComponent = React.lazy(() => {
  if (window.innerWidth > 768) {
    return import(/* webpackChunkName: "desktop-component" */ './DesktopComponent')
  } else {
    return import(/* webpackChunkName: "mobile-component" */ './MobileComponent')
  }
})

// 预加载策略
class PreloadManager {
  constructor() {
    this.preloadedModules = new Set()
    this.preloadQueue = []
    this.isPreloading = false
  }

  // 预加载模块
  async preloadModule(importFn, priority = 'low') {
    const moduleKey = importFn.toString()
    
    if (this.preloadedModules.has(moduleKey)) {
      return
    }

    this.preloadQueue.push({ importFn, priority, moduleKey })
    
    if (!this.isPreloading) {
      this.processPreloadQueue()
    }
  }

  // 处理预加载队列
  async processPreloadQueue() {
    this.isPreloading = true
    
    // 按优先级排序
    this.preloadQueue.sort((a, b) => {
      const priorities = { high: 3, medium: 2, low: 1 }
      return priorities[b.priority] - priorities[a.priority]
    })
    
    while (this.preloadQueue.length > 0) {
      const { importFn, moduleKey } = this.preloadQueue.shift()
      
      try {
        // 在空闲时间预加载
        await this.requestIdleCallback(() => importFn())
        this.preloadedModules.add(moduleKey)
      } catch (error) {
        console.warn('Preload failed:', error)
      }
    }
    
    this.isPreloading = false
  }

  // 请求空闲回调
  requestIdleCallback(callback) {
    return new Promise(resolve => {
      if ('requestIdleCallback' in window) {
        requestIdleCallback(() => {
          callback()
          resolve()
        })
      } else {
        setTimeout(() => {
          callback()
          resolve()
        }, 0)
      }
    })
  }

  // 基于用户行为预加载
  preloadOnHover(element, importFn) {
    let timeoutId = null
    
    element.addEventListener('mouseenter', () => {
      timeoutId = setTimeout(() => {
        this.preloadModule(importFn, 'high')
      }, 100) // 100ms延迟避免误触
    })
    
    element.addEventListener('mouseleave', () => {
      if (timeoutId) {
        clearTimeout(timeoutId)
        timeoutId = null
      }
    })
  }

  // 基于视口预加载
  preloadOnVisible(element, importFn) {
    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            this.preloadModule(importFn, 'medium')
            observer.unobserve(entry.target)
          }
        })
      }, {
        rootMargin: '100px'
      })
      
      observer.observe(element)
    }
  }
}

// 智能代码分割
class SmartCodeSplitting {
  constructor() {
    this.preloadManager = new PreloadManager()
    this.routeAnalytics = new Map()
    this.init()
  }

  init() {
    this.trackRouteUsage()
    this.setupIntelligentPreloading()
  }

  // 跟踪路由使用情况
  trackRouteUsage() {
    // 记录路由访问
    const currentRoute = window.location.pathname
    const usage = this.routeAnalytics.get(currentRoute) || { count: 0, lastVisit: 0 }
    
    usage.count++
    usage.lastVisit = Date.now()
    
    this.routeAnalytics.set(currentRoute, usage)
    
    // 存储到localStorage
    localStorage.setItem('route-analytics', JSON.stringify([...this.routeAnalytics]))
  }

  // 智能预加载
  setupIntelligentPreloading() {
    // 基于历史数据预加载热门路由
    const analytics = this.getRouteAnalytics()
    const popularRoutes = this.getPopularRoutes(analytics)
    
    popularRoutes.forEach(route => {
      const importFn = this.getRouteImportFunction(route)
      if (importFn) {
        this.preloadManager.preloadModule(importFn, 'medium')
      }
    })
  }

  // 获取路由分析数据
  getRouteAnalytics() {
    try {
      const stored = localStorage.getItem('route-analytics')
      return stored ? new Map(JSON.parse(stored)) : new Map()
    } catch {
      return new Map()
    }
  }

  // 获取热门路由
  getPopularRoutes(analytics, limit = 3) {
    return [...analytics.entries()]
      .sort(([, a], [, b]) => b.count - a.count)
      .slice(0, limit)
      .map(([route]) => route)
  }

  // 获取路由导入函数
  getRouteImportFunction(route) {
    const routeMap = {
      '/about': () => import('./pages/About.vue'),
      '/products': () => import('./pages/Products.vue'),
      '/contact': () => import('./pages/Contact.vue')
    }
    
    return routeMap[route]
  }
}

// 初始化智能代码分割
const smartCodeSplitting = new SmartCodeSplitting()

总结

Web性能优化的核心要点:

  1. 性能监控:Core Web Vitals指标、性能预算管理
  2. 资源优化:图片优化、字体优化、压缩策略
  3. 代码分割:路由级分割、组件级分割、智能预加载
  4. 缓存策略:浏览器缓存、CDN缓存、Service Worker
  5. 网络优化:HTTP/2、资源预加载、关键资源优先级

性能优化是一个持续的过程,需要结合具体业务场景和用户需求,制定合适的优化策略。通过系统性的性能监控和优化,可以显著提升用户体验和业务指标。