Skip to content

JavaScript 性能优化实战技巧

在现代 Web 开发中,性能优化是一个永恒的话题。今天我将分享 10 个实用的 JavaScript 性能优化技巧,这些都是我在实际项目中验证过的有效方法。

1. 避免频繁的 DOM 操作

DOM 操作是性能瓶颈的主要来源之一。

❌ 性能较差的写法

javascript
// 每次循环都会触发重绘
for (let i = 0; i < 1000; i++) {
  document.getElementById('list').innerHTML += `<li>Item ${i}</li>`
}

✅ 优化后的写法

javascript
// 使用 DocumentFragment 批量操作
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li')
  li.textContent = `Item ${i}`
  fragment.appendChild(li)
}
document.getElementById('list').appendChild(fragment)

2. 合理使用防抖和节流

对于高频触发的事件,使用防抖和节流可以显著提升性能。

防抖实现

javascript
function debounce(func, wait) {
  let timeout
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout)
      func(...args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}

// 使用示例
const searchInput = document.getElementById('search')
const debouncedSearch = debounce((value) => {
  // 执行搜索逻辑
  console.log('搜索:', value)
}, 300)

searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value)
})

节流实现

javascript
function throttle(func, limit) {
  let inThrottle
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args)
      inThrottle = true
      setTimeout(() => inThrottle = false, limit)
    }
  }
}

// 滚动事件节流
window.addEventListener('scroll', throttle(() => {
  console.log('滚动事件触发')
}, 100))

3. 优化循环性能

使用高效的循环方式

javascript
const arr = new Array(1000000).fill(0).map((_, i) => i)

// 性能测试
console.time('for loop')
for (let i = 0, len = arr.length; i < len; i++) {
  // 处理逻辑
}
console.timeEnd('for loop')

console.time('for...of')
for (const item of arr) {
  // 处理逻辑
}
console.timeEnd('for...of')

// 对于简单操作,原生方法通常更快
console.time('forEach')
arr.forEach(item => {
  // 处理逻辑
})
console.timeEnd('forEach')

4. 内存管理和垃圾回收

避免内存泄漏

javascript
// ❌ 可能导致内存泄漏
function createHandler() {
  const largeData = new Array(1000000).fill('data')
  
  return function(event) {
    // 这个闭包会保持对 largeData 的引用
    console.log('Event handled')
  }
}

// ✅ 优化后的写法
function createHandler() {
  return function(event) {
    console.log('Event handled')
  }
}

// 及时清理事件监听器
const controller = new AbortController()
element.addEventListener('click', handler, {
  signal: controller.signal
})

// 在不需要时取消监听
controller.abort()

5. 使用 Web Workers 处理密集计算

javascript
// worker.js
self.onmessage = function(e) {
  const { data } = e
  const result = heavyComputation(data)
  self.postMessage(result)
}

function heavyComputation(data) {
  // 执行耗时计算
  let result = 0
  for (let i = 0; i < data.length; i++) {
    result += Math.sqrt(data[i])
  }
  return result
}

// main.js
const worker = new Worker('worker.js')
const largeDataSet = new Array(1000000).fill().map(() => Math.random() * 100)

worker.postMessage(largeDataSet)
worker.onmessage = function(e) {
  console.log('计算结果:', e.data)
}

6. 图片懒加载实现

javascript
class LazyLoader {
  constructor() {
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      { threshold: 0.1 }
    )
    this.init()
  }

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

  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target
        img.src = img.dataset.src
        img.removeAttribute('data-src')
        this.observer.unobserve(img)
      }
    })
  }
}

// 使用
new LazyLoader()

7. 缓存策略优化

javascript
class CacheManager {
  constructor(maxSize = 100) {
    this.cache = new Map()
    this.maxSize = maxSize
  }

  get(key) {
    if (this.cache.has(key)) {
      // LRU: 将访问的项移到最后
      const value = this.cache.get(key)
      this.cache.delete(key)
      this.cache.set(key, value)
      return value
    }
    return null
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key)
    } else if (this.cache.size >= this.maxSize) {
      // 删除最久未使用的项
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, value)
  }
}

// API 请求缓存
const apiCache = new CacheManager(50)

async function fetchWithCache(url) {
  const cached = apiCache.get(url)
  if (cached) {
    return cached
  }

  const response = await fetch(url)
  const data = await response.json()
  apiCache.set(url, data)
  return data
}

8. 代码分割和懒加载

javascript
// 动态导入实现代码分割
async function loadFeature() {
  const { heavyFeature } = await import('./heavy-feature.js')
  return heavyFeature
}

// 路由级别的懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import('./components/Dashboard.vue')
  },
  {
    path: '/profile',
    component: () => import('./components/Profile.vue')
  }
]

9. 虚拟列表实现

javascript
class VirtualList {
  constructor(container, itemHeight, items) {
    this.container = container
    this.itemHeight = itemHeight
    this.items = items
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight)
    this.startIndex = 0
    
    this.init()
  }

  init() {
    this.container.style.height = `${this.items.length * this.itemHeight}px`
    this.container.style.position = 'relative'
    this.container.style.overflow = 'auto'
    
    this.container.addEventListener('scroll', this.handleScroll.bind(this))
    this.render()
  }

  handleScroll() {
    const scrollTop = this.container.scrollTop
    this.startIndex = Math.floor(scrollTop / this.itemHeight)
    this.render()
  }

  render() {
    const endIndex = Math.min(
      this.startIndex + this.visibleCount + 1,
      this.items.length
    )
    
    this.container.innerHTML = ''
    
    for (let i = this.startIndex; i < endIndex; i++) {
      const item = document.createElement('div')
      item.style.position = 'absolute'
      item.style.top = `${i * this.itemHeight}px`
      item.style.height = `${this.itemHeight}px`
      item.textContent = this.items[i]
      this.container.appendChild(item)
    }
  }
}

10. 性能监控和分析

javascript
class PerformanceMonitor {
  constructor() {
    this.metrics = {}
    this.init()
  }

  init() {
    // 监控页面加载时间
    window.addEventListener('load', () => {
      const navigation = performance.getEntriesByType('navigation')[0]
      this.metrics.loadTime = navigation.loadEventEnd - navigation.fetchStart
      this.report()
    })

    // 监控长任务
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
          if (entry.duration > 50) {
            console.warn('Long task detected:', entry)
          }
        })
      })
      observer.observe({ entryTypes: ['longtask'] })
    }
  }

  // 测量函数执行时间
  measure(name, fn) {
    const start = performance.now()
    const result = fn()
    const end = performance.now()
    this.metrics[name] = end - start
    return result
  }

  // 上报性能数据
  report() {
    console.log('Performance Metrics:', this.metrics)
    // 可以发送到分析服务
  }
}

// 使用示例
const monitor = new PerformanceMonitor()

monitor.measure('dataProcessing', () => {
  // 执行数据处理逻辑
  return processLargeDataSet()
})

总结

这些性能优化技巧涵盖了 JavaScript 开发的各个方面:

  1. DOM 操作优化 - 减少重绘重排
  2. 事件处理优化 - 防抖节流
  3. 循环性能优化 - 选择合适的循环方式
  4. 内存管理 - 避免内存泄漏
  5. 并发处理 - Web Workers
  6. 资源加载 - 懒加载
  7. 缓存策略 - 提高响应速度
  8. 代码分割 - 按需加载
  9. 虚拟化 - 处理大量数据
  10. 性能监控 - 持续优化

在实际应用中,我们需要根据具体场景选择合适的优化策略,并通过性能测试验证优化效果。记住,过早优化是万恶之源,先保证功能正确,再针对性能瓶颈进行优化。

希望这些技巧能够帮助你写出更高性能的 JavaScript 代码!

Part of the TimeTofu ecosystem - helping you invest your attention wisely.