每天一个高级前端知识 - Day 27

4 阅读5分钟

每天一个高级前端知识 - Day 27

今日主题:前端性能优化的极致 - 从浏览器原理到实战技巧

核心概念:性能优化不是技巧堆砌,而是对浏览器渲染机制的深刻理解

真正的性能优化是从输入 URL 到像素绘制的全链路优化,每一毫秒的节省都需要对浏览器底层机制的精准把握。

🔬 浏览器渲染全链路

输入 URL
    ↓
DNS 解析 (20-120ms)
    ↓
TCP 连接 (三次握手 + TLS)
    ↓
请求响应 (TTFB)
    ↓
HTML 解析 → DOM 树
    ↓
CSS 解析 → CSSOM 树 → 渲染树
    ↓
布局 Layout (重排)
    ↓
绘制 Paint (重绘)
    ↓
合成 Composite
    ↓
屏幕显示

⚡ 网络层极致优化

// ============ 1. DNS 预解析与预连接 ============
// 在 HTML head 中添加
const addResourceHints = () => {
  // DNS 预解析(提前解析第三方域名)
  const dnsPrefetch = document.createElement('link');
  dnsPrefetch.rel = 'dns-prefetch';
  dnsPrefetch.href = 'https://api.example.com';
  document.head.appendChild(dnsPrefetch);
  
  // 预连接(DNS + TCP + TLS)
  const preconnect = document.createElement('link');
  preconnect.rel = 'preconnect';
  preconnect.href = 'https://cdn.example.com';
  preconnect.crossOrigin = 'anonymous';
  document.head.appendChild(preconnect);
  
  // 预加载关键资源
  const preload = document.createElement('link');
  preload.rel = 'preload';
  preload.as = 'font';
  preload.href = '/fonts/main.woff2';
  preload.crossOrigin = 'anonymous';
  document.head.appendChild(preload);
  
  // 预获取(空闲时加载)
  const prefetch = document.createElement('link');
  prefetch.rel = 'prefetch';
  prefetch.as = 'document';
  prefetch.href = '/next-page';
  document.head.appendChild(prefetch);
  
  // 预渲染(极速跳转,谨慎使用)
  const prerender = document.createElement('link');
  prerender.rel = 'prerender';
  prerender.href = '/likely-next-page';
  document.head.appendChild(prerender);
};

// ============ 2. HTTP/2 多路复用与服务器推送 ============
// 服务器配置 (nginx)
// http2_push_preload on;
// location / {
//     http2_push /css/style.css;
//     http2_push /js/main.js;
// }

// ============ 3. 资源优先级调度 ============
class ResourceScheduler {
  constructor() {
    this.highPriority = new Set();
    this.normalPriority = new Set();
    this.lowPriority = new Set();
    
    this.observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        this.adjustPriority(entry);
      }
    });
    this.observer.observe({ entryTypes: ['resource'] });
  }
  
  adjustPriority(entry) {
    // 首屏关键资源提高优先级
    const criticalResources = ['main.js', 'vendor.js', 'style.css'];
    const url = entry.name.split('/').pop();
    
    if (criticalResources.includes(url) && entry.delay > 0) {
      // 使用 Priority Hints API
      if ('priority' in entry) {
        entry.priority = 'high';
      }
    }
  }
  
  // 延迟加载非关键资源
  lazyLoadResources() {
    const lazyResources = document.querySelectorAll('[data-lazy]');
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const resource = entry.target;
          const url = resource.dataset.src;
          if (url) {
            const link = document.createElement('link');
            link.rel = 'preload';
            link.as = resource.dataset.as || 'image';
            link.href = url;
            document.head.appendChild(link);
          }
          observer.unobserve(resource);
        }
      });
    });
    
    lazyResources.forEach(resource => observer.observe(resource));
  }
}

🎨 渲染层极致优化

// ============ 1. 关键渲染路径优化 ============
// 内联关键 CSS
const inlineCriticalCSS = () => {
  // 提取首屏所需 CSS(使用 Critical CLI)
  // critical --base . --index index.html --inline --minify
};

// 延迟非关键 CSS
const loadNonCriticalCSS = () => {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '/css/non-critical.css';
  link.media = 'print'; // 先使用 print 媒体类型,不会阻塞渲染
  link.onload = () => link.media = 'all';
  document.head.appendChild(link);
};

// ============ 2. 避免布局抖动 (Layout Thrashing) ============
class LayoutOptimizer {
  constructor() {
    this.reads = [];
    this.writes = [];
    this.rafId = null;
  }
  
  // 批量读取 DOM 属性
  read(operation) {
    this.reads.push(operation);
    this.schedule();
  }
  
  // 批量写入 DOM 属性
  write(operation) {
    this.writes.push(operation);
    this.schedule();
  }
  
  schedule() {
    if (this.rafId) return;
    this.rafId = requestAnimationFrame(() => {
      // 先批量读取
      this.reads.forEach(read => read());
      this.reads = [];
      
      // 再批量写入
      this.writes.forEach(write => write());
      this.writes = [];
      
      this.rafId = null;
    });
  }
}

const layoutOptimizer = new LayoutOptimizer();

// ❌ 糟糕的写法 - 导致多次强制同步布局
function badResize(elements) {
  elements.forEach(el => {
    const width = el.offsetWidth; // 读
    el.style.width = width + 10 + 'px'; // 写 → 强制布局
    const height = el.offsetHeight; // 读 → 再次强制布局
    el.style.height = height + 10 + 'px';
  });
}

// ✅ 优化写法 - 批量读写
function goodResize(elements) {
  // 先批量读取
  const sizes = elements.map(el => ({
    width: el.offsetWidth,
    height: el.offsetHeight,
    el
  }));
  
  // 再批量写入
  requestAnimationFrame(() => {
    sizes.forEach(({ el, width, height }) => {
      el.style.width = width + 10 + 'px';
      el.style.height = height + 10 + 'px';
    });
  });
}

// ============ 3. GPU 加速与合成层提升 ============
const gpuAcceleration = {
  // 触发 GPU 合成的属性
  triggers: [
    'transform: translateZ(0)',
    'transform: translate3d(0,0,0)',
    'will-change: transform',
    'will-change: opacity'
  ],
  
  // 动画使用 transform 和 opacity
  // ❌ 差
  // element.style.left = '100px';
  
  // ✅ 好
  // element.style.transform = 'translateX(100px)';
  
  // 合成层爆炸的预防
  preventLayerExplosion() {
    // 限制合成层数量,使用 contain 属性
    // .list-item {
    //   contain: layout style paint;
    // }
  }
};

// ============ 4. 虚拟滚动 (处理大数据列表) ============
class VirtualScroller {
  constructor(container, options) {
    this.container = container;
    this.itemHeight = options.itemHeight;
    this.renderItem = options.renderItem;
    this.totalItems = options.totalItems;
    this.bufferSize = options.bufferSize || 5;
    
    this.scrollTop = 0;
    this.startIndex = 0;
    this.endIndex = 0;
    this.rafId = null;
    
    this.setupDOM();
    this.bindEvents();
    this.render();
  }
  
  setupDOM() {
    this.viewport = document.createElement('div');
    this.viewport.style.height = '100%';
    this.viewport.style.overflow = 'auto';
    this.viewport.style.position = 'relative';
    this.viewport.style.willChange = 'transform';
    
    this.content = document.createElement('div');
    this.content.style.position = 'absolute';
    this.content.style.top = '0';
    this.content.style.left = '0';
    this.content.style.width = '100%';
    this.content.style.willChange = 'transform';
    
    this.container.appendChild(this.viewport);
    this.viewport.appendChild(this.content);
    
    // 设置总高度
    const totalHeight = this.totalItems * this.itemHeight;
    this.content.style.height = `${totalHeight}px`;
  }
  
  bindEvents() {
    // 使用 passive 提升滚动性能
    this.viewport.addEventListener('scroll', () => {
      if (this.rafId) return;
      this.rafId = requestAnimationFrame(() => {
        this.scrollTop = this.viewport.scrollTop;
        this.updateVisibleRange();
        this.rafId = null;
      });
    }, { passive: true });
  }
  
  updateVisibleRange() {
    const viewportHeight = this.viewport.clientHeight;
    const start = Math.floor(this.scrollTop / this.itemHeight);
    const visibleCount = Math.ceil(viewportHeight / this.itemHeight);
    
    this.startIndex = Math.max(0, start - this.bufferSize);
    this.endIndex = Math.min(
      this.totalItems - 1,
      start + visibleCount + this.bufferSize
    );
    
    this.render();
  }
  
  render() {
    const fragment = document.createDocumentFragment();
    const startOffset = this.startIndex * this.itemHeight;
    
    for (let i = this.startIndex; i <= this.endIndex; i++) {
      const item = this.renderItem(i);
      item.style.position = 'absolute';
      item.style.top = `${i * this.itemHeight - startOffset}px`;
      item.style.left = '0';
      item.style.width = '100%';
      item.style.height = `${this.itemHeight}px`;
      item.style.willChange = 'transform';
      fragment.appendChild(item);
    }
    
    // 清理 DOM 节点,避免内存泄漏
    while (this.content.firstChild) {
      this.content.removeChild(this.content.firstChild);
    }
    
    this.content.appendChild(fragment);
    this.content.style.transform = `translateY(${startOffset}px)`;
  }
}

📦 资源加载极致优化

// ============ 1. 智能图片加载策略 ============
class ImageOptimizer {
  constructor() {
    this.supportsWebP = null;
    this.supportsAVIF = null;
    this.init();
  }
  
  async init() {
    this.supportsWebP = await this.checkWebPSupport();
    this.supportsAVIF = await this.checkAVIFSupport();
  }
  
  async checkWebPSupport() {
    const canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 1;
    const ctx = canvas.getContext('2d');
    const data = ctx.getImageData(0, 0, 1, 1);
    return canvas.toDataURL('image/webp').indexOf('image/webp') === 5;
  }
  
  async checkAVIFSupport() {
    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => resolve(true);
      img.onerror = () => resolve(false);
      img.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
    });
  }
  
  getOptimalUrl(url) {
    if (this.supportsAVIF) {
      return url.replace(/\.(jpg|png|webp)/, '.avif');
    }
    if (this.supportsWebP) {
      return url.replace(/\.(jpg|png)/, '.webp');
    }
    return url;
  }
  
  // 响应式图片加载
  setupResponsiveImages() {
    const images = document.querySelectorAll('img[data-src]');
    
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          const src = img.dataset.src;
          const srcset = img.dataset.srcset;
          const sizes = img.dataset.sizes;
          
          if (srcset) {
            img.srcset = srcset;
          }
          if (sizes) {
            img.sizes = sizes;
          }
          img.src = src;
          
          // 图片加载完成后添加模糊到清晰的过渡
          img.style.filter = 'blur(10px)';
          img.onload = () => {
            img.style.transition = 'filter 0.3s';
            img.style.filter = 'blur(0)';
          };
          
          observer.unobserve(img);
        }
      });
    }, {
      rootMargin: '50px', // 提前50px加载
      threshold: 0.01
    });
    
    images.forEach(img => observer.observe(img));
  }
  
  // 使用新的 decoding 和 loading 属性
  optimizeImageAttributes() {
    document.querySelectorAll('img').forEach(img => {
      img.loading = 'lazy';
      img.decoding = 'async';
    });
  }
}

// ============ 2. 代码分割与动态导入策略 ============
const routeLoader = {
  // 基于路由的代码分割
  routes: {
    home: () => import(/* webpackChunkName: "home" */ './pages/Home'),
    about: () => import(/* webpackChunkName: "about" */ './pages/About'),
    dashboard: () => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
  },
  
  // 预加载策略
  preloadRoute(routeName) {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        this.routes[routeName]();
      });
    } else {
      setTimeout(() => this.routes[routeName](), 1);
    }
  },
  
  // 基于 hover 预加载
  setupPrefetchOnHover() {
    document.querySelectorAll('[data-route]').forEach(link => {
      link.addEventListener('mouseenter', () => {
        const route = link.dataset.route;
        this.preloadRoute(route);
      }, { once: true });
    });
  }
};

// ============ 3. 缓存策略 ============
class CacheStrategy {
  // Service Worker 缓存策略
  static getCacheStrategies() {
    return {
      // Cache First (优先缓存)
      cacheFirst: async (request) => {
        const cache = await caches.open('v1');
        const cached = await cache.match(request);
        if (cached) return cached;
        
        const response = await fetch(request);
        cache.put(request, response.clone());
        return response;
      },
      
      // Network First (优先网络)
      networkFirst: async (request) => {
        try {
          const response = await fetch(request);
          const cache = await caches.open('v1');
          cache.put(request, response.clone());
          return response;
        } catch (error) {
          const cached = await caches.match(request);
          return cached || new Response('Offline');
        }
      },
      
      // Stale While Revalidate (后台更新)
      staleWhileRevalidate: async (request) => {
        const cache = await caches.open('v1');
        const cached = await cache.match(request);
        
        const fetchPromise = fetch(request).then(async (response) => {
          cache.put(request, response.clone());
          return response;
        });
        
        return cached || fetchPromise;
      }
    };
  }
  
  // 使用 IndexedDB 缓存 API 数据
  async apiCache(key, fetcher, ttl = 60000) {
    const cached = await this.getFromDB(key);
    if (cached && Date.now() - cached.timestamp < ttl) {
      return cached.data;
    }
    
    const data = await fetcher();
    await this.saveToDB(key, data);
    return data;
  }
  
  private async getFromDB(key) {
    return new Promise((resolve) => {
      const request = indexedDB.open('apiCache', 1);
      request.onsuccess = () => {
        const db = request.result;
        const tx = db.transaction('cache', 'readonly');
        const store = tx.objectStore('cache');
        const getRequest = store.get(key);
        getRequest.onsuccess = () => resolve(getRequest.result);
      };
      request.onerror = () => resolve(null);
    });
  }
}

🎯 今日挑战

实现一个完整的性能优化工具库,要求:

  1. 资源预加载管理器(支持 DNS、preconnect、preload、prefetch)
  2. 虚拟滚动组件(支持百万级数据)
  3. 图片优化器(WebP/AVIF + 懒加载 + 响应式)
  4. 布局抖动检测与修复工具
  5. 性能监控与告警(Core Web Vitals)
  6. Service Worker 缓存策略
// 使用示例
const perfOptimizer = new PerfOptimizer({
  preload: {
    dns: ['https://api.example.com'],
    preconnect: ['https://cdn.example.com'],
    preload: [
      { href: '/fonts/main.woff2', as: 'font' },
      { href: '/css/critical.css', as: 'style' }
    ]
  },
  images: {
    lazyLoad: true,
    useWebP: true,
    quality: 80
  },
  virtualScroll: {
    enabled: true,
    itemHeight: 50
  }
});

perfOptimizer.init();

💡 性能箴言:"最快的请求是不发送的请求,最快的资源是不加载的资源。懒加载、预加载、缓存是性能优化的三驾马车。"