前端取经路——性能优化:唐僧的九道心经

发布于:2025-05-10 ⋅ 阅读:(23) ⋅ 点赞:(0)

大家好,我是老十三,一名前端开发工程师。性能优化如同唐僧的九道心经,是前端修行的精髓所在。在本文中,我将为你揭示从网络传输到渲染优化的九大关键技术,涵盖HTTP协议、资源加载策略、缓存控制等核心难题。通过这些实战技巧,你将能打造出如行云流水般顺滑的用户体验,让你的应用脱胎换骨,达到性能优化的"大乘境界"。

工程化之道修行完毕,我们继续西行,迎来前端取经第六关——性能优化。唐僧虽不像孙悟空那般神通广大,但他的九道心经却是取经团队的精神支柱。同样,前端性能优化虽不如框架技术那般光鲜,却是用户体验的根本保障,值得每位前端修行者深入探索。

🚀 第一难:网络优化 - HTTP/2与HTTP/3的"缩地术"

问题:为什么我的网站在弱网环境下体验差?如何利用现代网络协议提升加载速度?

深度技术:

网络传输是前端性能的第一道关卡,而HTTP协议的演进极大改变了资源加载效率。从HTTP/1.1到HTTP/2再到HTTP/3,每一代协议都带来了性能突破。

HTTP/2引入了多路复用、头部压缩、服务器推送等特性,彻底改变了我们对"减少HTTP请求"的传统优化思路;而基于UDP的HTTP/3(QUIC)进一步解决了队头阻塞问题,提供更可靠的弱网环境表现。理解这些协议的核心机制,才能设计出真正高效的前端资源加载策略。

代码示例:

// 1. HTTP/2服务器推送配置
// Nginx配置示例
server {
    listen 443 ssl http2;
    server_name example.com;
    
    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    // HTTP/2服务器推送
    location / {
        root /var/www/html;
        http2_push /styles/main.css;
        http2_push /scripts/main.js;
        http2_push /images/logo.png;
    }
}

// 2. Link预加载标头(替代服务器推送)
// Express.js中间件示例
app.get('/', (req, res) => {
  // 使用Link头预告关键资源
  res.set('Link', [
    '</styles/main.css>; rel=preload; as=style',
    '</scripts/main.js>; rel=preload; as=script',
    '</fonts/awesome.woff2>; rel=preload; as=font; crossorigin'
  ].join(', '));
  
  res.render('index');
});

// 3. 域名分片(HTTP/1.1时代的优化,HTTP/2下反而有害)
// bad.js - HTTP/2下不推荐
function loadImagesFromMultipleDomains() {
  const domains = [
    'assets1.example.com',
    'assets2.example.com',
    'assets3.example.com'
  ];
  
  return images.map((image, index) => {
    const domain = domains[index % domains.length];
    return `https://${domain}/${image.path}`;
  });
}

// good.js - HTTP/2下推荐
function loadImagesFromSingleDomain() {
  // 使用单一域名,利用HTTP/2多路复用
  return images.map(image => `https://assets.example.com/${image.path}`);
}

// 4. 资源合并(HTTP/1.1时代的优化,HTTP/2下需重新评估)
// webpack.config.js
module.exports = {
  // HTTP/1.1环境: 推荐合并文件减少请求数
  // HTTP/2环境: 适度拆分,利用并行加载
  optimization: {
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: 10, // HTTP/2下可以设置更高
      maxAsyncRequests: 10,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

// 5. 使用CDN加速
// index.html中引用CDN资源
<script src="https://cdn.example.com/libraries/react.production.min.js"></script>

// 配置CDN回源
// 使用源站防护,控制CDN回源流量
const config = {
  cdnDomain: 'cdn.example.com',
  // 避免直接访问源站
  preventDirectAccess: process.env.NODE_ENV === 'production',
  generateUrl(path) {
    return `https://${this.cdnDomain}/${path}`;
  }
};

// 6. 分析并优化第三方资源
// 第三方资源审计脚本
function auditThirdPartyResources() {
  const resources = performance.getEntriesByType('resource');
  
  // 按域名分组统计
  const domainStats = {};
  
  resources.forEach(resource => {
    const url = new URL(resource.name);
    const domain = url.hostname;
    
    if (!domainStats[domain]) {
      domainStats[domain] = {
        count: 0,
        totalSize: 0,
        resources: []
      };
    }
    
    domainStats[domain].count++;
    domainStats[domain].totalSize += resource.encodedBodySize || 0;
    domainStats[domain].resources.push({
      url: resource.name,
      size: resource.encodedBodySize || 0,
      duration: resource.duration,
      initiatorType: resource.initiatorType
    });
  });
  
  console.table(
    Object.entries(domainStats)
      .map(([domain, stats]) => ({
        Domain: domain,
        Requests: stats.count,
        'Total Size (KB)': (stats.totalSize / 1024).toFixed(2),
        'Is Third Party': !domain.includes(window.location.hostname)
      }))
  );
}

// 7. 实现HTTP/3检测和回退
function detectHttp3Support() {
  return new Promise(resolve => {
    const img = new Image();
    const start = performance.now();
    let http3Supported = false;
    
    // 尝试通过HTTP/3加载1x1像素图片
    img.src = 'https://http3-test.example.com/pixel.png?cachebust=' + Math.random();
    
    // 如果加载时间短,可能支持HTTP/3
    img.onload = () => {
      const loadTime = performance.now() - start;
      http3Supported = loadTime < 50; // 假设50ms是阈值
      resolve(http3Supported);
    };
    
    img.onerror = () => {
      resolve(false);
    };
    
    // 如果超过1秒还没加载完,认为不支持
    setTimeout(() => {
      if (!http3Supported) {
        resolve(false);
      }
    }, 1000);
  });
}

📊 第二难:加载策略 - 资源优先级的"火眼金睛"

问题:加载顺序如何影响用户体验?如何只加载当前页面真正需要的资源?

深度技术:

网页加载是一场与时间赛跑的过程,而资源优先级策略决定了这场比赛的成败。关键在于理解渲染路径和资源加载对页面展示的影响,确保首屏关键内容最先加载完成。

现代浏览器提供了多种资源提示(Resource Hints)机制:preload(预加载)、prefetch(预获取)、preconnect(预连接)等,正确使用这些技术可以精确控制资源加载顺序和时机。同时,按需加载和代码分割(Code Splitting)策略也是优化大型应用加载性能的关键手段。

代码示例:

<!-- 1. 资源提示示例 -->
<!-- Preload - 当前页面必需的高优先级资源 -->
<link rel="preload" href="/fonts/awesome.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/hero-image.jpg" as="image" media="(min-width: 600px)">

<!-- Prefetch - 可能在未来页面用到的资源 -->
<link rel="prefetch" href="/next-page.js">
<link rel="prefetch" href="/articles/popular.json">

<!-- Preconnect - 提前建立连接 -->
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://analytics.example.com">

<!-- 预渲染下一页 -->
<link rel="prerender" href="https://example.com/next-page">

<!-- 2. 关键CSS内联 -->
<style>
  /* 首屏关键样式内联 */
  .hero {
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #f0f0f0;
  }
  
  .hero-title {
    font-size: 2.5rem;
    font-weight: bold;
    color: #333;
  }
  
  /* 其他首屏必需样式... */
</style>

<!-- 异步加载非关键CSS -->
<link rel="preload" href="/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/main.css"></noscript>

<!-- 3. 脚本加载策略 -->
<!-- 延迟执行,不阻塞解析 -->
<script src="/analytics.js" defer></script>

<!-- 异步加载,不阻塞解析,下载完立即执行 -->
<script src="/widget.js" async></script>

<!-- 模块脚本,自动延迟 -->
<script type="module" src="/app.js"></script>
<script nomodule src="/app-legacy.js"></script>

<!-- 内联关键脚本 -->
<script>
  // 初始化首屏必需功能
  document.addEventListener('DOMContentLoaded', () => {
    initCriticalFeatures();
  });
</script>

<!-- 4. 图片资源策略 -->
<!-- 响应式图片 -->
<picture>
  <source srcset="/hero-large.webp 1200w, /hero-medium.webp 800w" type="image/webp">
  <source srcset="/hero-large.jpg 1200w, /hero-medium.jpg 800w" type="image/jpeg"> 
  <img src="/hero-fallback.jpg" alt="Hero image" loading="eager" width="1200" height="600">
</picture>

<!-- 延迟加载非首屏图片 -->
<img src="placeholder.svg" data-src="product.jpg" alt="Product" loading="lazy" class="lazyload">

<!-- 5. JavaScript实现资源优先级管理 -->
<script>
// 动态插入预加载
function preloadCriticalAssets() {
  const assets = [
    { href: '/app.js', as: 'script' },
    { href: '/logo.svg', as: 'image' }
  ];
  
  assets.forEach(asset => {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.href = asset.href;
    link.as = asset.as;
    if (asset.as === 'font') {
      link.crossOrigin = 'anonymous';
    }
    document.head.appendChild(link);
  });
}

// 根据路由预测和预加载
function predictNextNavigation() {
  // 分析用户行为,预测下一个导航目标
  const links = Array.from(document.querySelectorAll('a'));
  const visibleLinks = links.filter(link => {
    const rect = link.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= window.innerHeight &&
      rect.right <= window.innerWidth
    );
  });
  
  // 为可视区域内的链接添加prefetch
  visibleLinks.forEach(link => {
    const href = link.getAttribute('href');
    if (href && href.startsWith('/') && !link.dataset.prefetched) {
      const prefetchLink = document.createElement('link');
      prefetchLink.rel = 'prefetch';
      prefetchLink.href = href;
      document.head.appendChild(prefetchLink);
      link.dataset.prefetched = 'true';
    }
  });
}

// 优先级队列实现
class ResourcePriorityQueue {
  constructor() {
    this.highPriority = [];
    this.mediumPriority = [];
    this.lowPriority = [];
    this.loaded = new Set();
  }
  
  add(resource, priority = 'medium') {
    if (this.loaded.has(resource.url)) return;
    
    switch(priority) {
      case 'high':
        this.highPriority.push(resource);
        break;
      case 'low':
        this.lowPriority.push(resource);
        break;
      default:
        this.mediumPriority.push(resource);
    }
  }
  
  processNext() {
    const resource = this.highPriority.shift() || 
                     this.mediumPriority.shift() || 
                     this.lowPriority.shift();
    
    if (!resource) return Promise.resolve();
    
    return this.loadResource(resource)
      .then(() => {
        this.loaded.add(resource.url);
        return this.processNext();
      })
      .catch(err => {
        console.error(`Failed to load ${resource.url}:`, err);
        return this.processNext();
      });
  }
  
  loadResource(resource) {
    // 根据资源类型选择加载策略
    switch(resource.type) {
      case 'script':
        return this.loadScript(resource.url, resource.async);
      case 'style':
        return this.loadStyle(resource.url);
      case 'image':
        return this.loadImage(resource.url);
      default:
        return this.loadGeneric(resource.url);
    }
  }
  
  // 各种资源加载实现...
  loadScript(url, async = false) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = url;
      script.async = async;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }
  
  loadStyle(url) {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = url;
      link.onload = resolve;
      link.onerror = reject;
      document.head.appendChild(link);
    });
  }
  
  // 其他加载方法...
}

// 使用示例
document.addEventListener('DOMContentLoaded', () => {
  const queue = new ResourcePriorityQueue();
  
  // 添加各种优先级的资源
  queue.add({ url: '/critical.js', type: 'script' }, 'high');
  queue.add({ url: '/menu.js', type: 'script' }, 'medium');
  queue.add({ url: '/analytics.js', type: 'script' }, 'low');
  
  // 开始处理队列
  queue.processNext();
  
  // 视口可见性变化时处理低优先级资源
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') {
      queue.processNext();
    }
  });
});
</script>

🗄️ 第三难:缓存控制 - 浏览器存储的"八卦炉"

问题:如何合理利用浏览器缓存加速页面访问?不同缓存策略在什么场景下最有效?

深度技术:

浏览器缓存是性能优化的强大工具,正确的缓存策略可以显著减少网络请求,加速页面加载。前端缓存策略需要考虑多个层面:HTTP缓存(通过Cache-Control和ETag控制)、ServiceWorker离线缓存、以及浏览器存储API(LocalStorage、SessionStorage、IndexedDB)。

设计缓存策略的关键在于平衡缓存时效性和命中率:频繁变化的内容需要短缓存或验证缓存,而静态资源则可使用长缓存配合内容哈希。理解不同缓存机制的特点和适用场景,才能构建出最优的缓存架构。

代码示例:

// 1. HTTP缓存控制
// nginx服务器配置
server {
    // 长缓存静态资源(带版本号或哈希的文件)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, max-age=31536000, immutable";
        etag off;
        access_log off;
    }
    
    // HTML文件使用验证缓存
    location ~* \.html$ {
        add_header Cache-Control "no-cache";
        add_header ETag ""; // 启用ETag
    }
    
    // API响应不缓存
    location /api/ {
        add_header Cache-Control "no-store";
        add_header Pragma "no-cache";
    }
}

// Express服务器配置
app.use('/static', express.static('public', {
  maxAge: '1y',
  etag: false,
  lastModified: false,
  setHeaders: (res, path) => {
    if (path.includes('chunk.') || path.includes('vendor.')) {
      // 包含哈希的文件使用不变缓存
      res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    }
  }
}));

// 2. Service Worker缓存
// sw.js - Service Worker文件
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/main.js',
  '/images/logo.png'
];

// 安装Service Worker
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

// 缓存优先,网络回退策略
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // 缓存命中
        if (response) {
          return response;
        }
        
        // 缓存未命中,发起网络请求
        return fetch(event.request).then(
          networkResponse => {
            // 检查是否为有效响应
            if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') {
              return networkResponse;
            }
            
            // 复制响应,因为响应流只能被读取一次
            const responseToCache = networkResponse.clone();
            
            caches.open(CACHE_NAME)
              .then(cache => {
                // 只缓存同源资源
                if (new URL(event.request.url).origin === location.origin) {
                  cache.put(event.request, responseToCache);
                }
              });
            
            return networkResponse;
          }
        );
      })
  );
});

// 更新Service Worker时清理旧缓存
self.addEventListener('activate', event => {
  const cacheWhitelist = [CACHE_NAME];
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

// 注册Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(registration => {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
      })
      .catch(error => {
        console.log('ServiceWorker registration failed: ', error);
      });
  });
}

// 3. 浏览器存储API
// LocalStorage - 持久化简单数据
const StorageManager = {
  // 设置带过期时间的缓存项
  setItem(key, value, expiryInMinutes = 60) {
    const item = {
      value,
      expiry: expiryInMinutes ? Date.now() + (expiryInMinutes * 60000) : null
    };
    localStorage.setItem(key, JSON.stringify(item));
  },
  
  // 获取数据,自动检查是否过期
  getItem(key) {
    const itemStr = localStorage.getItem(key);
    if (!itemStr) return null;
    
    try {
      const item = JSON.parse(itemStr);
      if (item.expiry && Date.now() > item.expiry) {
        localStorage.removeItem(key);
        return null;
      }
      return item.value;
    } catch (e) {
      console.error('Error parsing storage item:', e);
      return null;
    }
  },
  
  // 移除数据
  removeItem(key) {
    localStorage.removeItem(key);
  },
  
  // 清理所有过期项
  clearExpired() {
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      this.getItem(key); // 会自动检查和清理过期项
    }
  }
};

// 使用示例
StorageManager.setItem('user-preferences', { theme: 'dark', fontSize: 'large' }, 1440); // 24小时过期
const preferences = StorageManager.getItem('user-preferences');

// 4. IndexedDB - 存储大量结构化数据
class IndexedDBStorage {
  constructor(dbName, version) {
    this.dbName = dbName;
    this.version = version;
    this.db = null;
  }
  
  open() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // 创建存储对象
        if (!db.objectStoreNames.contains('articles')) {
          const store = db.createObjectStore('articles', { keyPath: 'id' });
          store.createIndex('by_date', 'date');
        }
      };
    });
  }
  
  // 保存文章
  saveArticle(article) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['articles'], 'readwrite');
      const store = transaction.objectStore('articles');
      
      // 添加或更新文章
      const request = store.put({
        ...article,
        cacheDate: new Date()
      });
      
      request.onsuccess = () => resolve(article);
      request.onerror = () => reject(request.error);
    });
  }
  
  // 获取文章
  getArticle(id) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['articles']);
      const store = transaction.objectStore('articles');
      const request = store.get(id);
      
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
  
  // 获取符合日期范围的文章
  getArticlesByDateRange(startDate, endDate) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['articles']);
      const store = transaction.objectStore('articles');
      const index = store.index('by_date');
      const range = IDBKeyRange.bound(startDate, endDate);
      const request = index.openCursor(range);
      
      const articles = [];
      
      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
          articles.push(cursor.value);
          cursor.continue();
        } else {
          resolve(articles);
        }
      };
      
      request.onerror = () => reject(request.error);
    });
  }
}

// 使用示例
async function cacheArticles() {
  const db = new IndexedDBStorage('content-db', 1);
  await db.open();
  
  try {
    // 获取文章并缓存
    const response = await fetch('/api/articles');
    const articles = await response.json();
    
    for (const article of articles) {
      await db.saveArticle(article);
    }
    
    console.log('All articles cached successfully');
  } catch (error) {
    console.error('Error caching articles:', error);
  }
}

// 5. 高级缓存策略实现
class CacheStrategy {
  constructor() {
    this.strategies = {
      cacheFirst: this.cacheFirst.bind(this),
      networkFirst: this.networkFirst.bind(this),
      staleWhileRevalidate: this.staleWhileRevalidate.bind(this)
    };
  }
  
  // 缓存优先,适用于不经常变化的资源
  async cacheFirst(url, cacheName) {
    const cache = await caches.open(cacheName);
    const cachedResponse = await cache.match(url);
    
    if (cachedResponse) {
      return cachedResponse;
    }
    
    const networkResponse = await fetch(url);
    cache.put(url, networkResponse.clone());
    return networkResponse;
  }
  
  // 网络优先,适用于经常更新的内容
  async networkFirst(url, cacheName, timeoutMs = 3000) {
    const cache = await caches.open(cacheName);
    
    try {
      // 设置超时,避免网络请求过长
      const networkPromise = fetch(url);
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Network timeout')), timeoutMs);
      });
      
      // 竞态Promise,哪个先完成用哪个
      const networkResponse = await Promise.race([networkPromise, timeoutPromise]);
      cache.put(url, networkResponse.clone());
      return networkResponse;
    } catch (error) {
      const cachedResponse = await cache.match(url);
      if (cachedResponse) {
        return cachedResponse;
      }
      throw error;
    }
  }
  
  // 边用缓存边更新,平衡速度和新鲜度
  async staleWhileRevalidate(url, cacheName) {
    const cache = await caches.open(cacheName);
    
    // 立即返回缓存(如果有)
    const cachedResponse = await cache.match(url);
    
    // 无论是否有缓存,都触发网络更新
    const updatePromise = fetch(url).then(networkResponse => {
      cache.put(url, networkResponse.clone());
      return networkResponse;
    });
    
    // 如果有缓存就返回缓存,否则等待网络请求
    return cachedResponse || updatePromise;
  }
  
  // 根据URL选择合适的策略
  async fetch(url, options = {}) {
    const { strategy = 'networkFirst', cacheName = 'default-cache' } = options;
    
    if (!this.strategies[strategy]) {
      throw new Error(`Unknown cache strategy: ${strategy}`);
    }
    
    return this.strategies[strategy](url, cacheName);
  }
}

// 使用示例
const cacheManager = new CacheStrategy();

// 对API请求使用网络优先策略
async function fetchUserData(userId) {
  try {
    const response = await cacheManager.fetch(`/api/users/${userId}`, {
      strategy: 'networkFirst',
      cacheName: 'api-cache'
    });
    return response.json();
  } catch (error) {
    console.error('Failed to fetch user data:', error);
    return null;
  }
}

// 对静态资源使用缓存优先策略
async function fetchStaticContent(path) {
  try {
    const response = await cacheManager.fetch(`/static/${path}`, {
      strategy: 'cacheFirst',
      cacheName: 'static-cache'
    });
    return response;
  } catch (error) {
    console.error(`Failed to fetch static content: ${path}`, error);
    return null;
  }
}

🖼️ 第四难:图片优化 - 下一代格式的"缩骨术"

问题:如何在不影响图片质量的前提下,显著减少图片体积?现代图片格式和优化技术能带来哪些提升?

深度技术:

图片优化是前端性能优化的重要环节,现代图片格式如WebP、AVIF等提供了更好的压缩率,而响应式图片技术则能根据设备特性提供最优图片。理解图片格式特性、压缩原理和加载策略,是提升页面性能的关键。

代码示例:

// 1. 响应式图片实现
<picture>
  <!-- WebP格式,支持时优先使用 -->
  <source
    srcset="
      /images/hero-400.webp 400w,
      /images/hero-800.webp 800w,
      /images/hero-1200.webp 1200w
    "
    type="image/webp"
    sizes="(max-width: 400px) 400px,
           (max-width: 800px) 800px,
           1200px"
  >
  
  <!-- 传统格式回退 -->
  <source
    srcset="
      /images/hero-400.jpg 400w,
      /images/hero-800.jpg 800w,
      /images/hero-1200.jpg 1200w
    "
    type="image/jpeg"
    sizes="(max-width: 400px) 400px,
           (max-width: 800px) 800px,
           1200px"
  >
  
  <!-- 最终回退方案 -->
  <img
    src="/images/hero-400.jpg"
    alt="Hero image"
    loading="eager"
    width="1200"
    height="600"
  >
</picture>

// 2. 图片懒加载实现
class LazyLoader {
  constructor(options = {}) {
    this.options = {
      root: null,
      rootMargin: '0px',
      threshold: 0.1,
      ...options
    };
    
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      this.options
    );
  }
  
  observe(elements) {
    elements.forEach(element => {
      if (element.dataset.src) {
        this.observer.observe(element);
      }
    });
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        this.loadImage(entry.target);
        this.observer.unobserve(entry.target);
      }
    });
  }
  
  loadImage(element) {
    const src = element.dataset.src;
    if (!src) return;
    
    // 创建新图片对象预加载
    const img = new Image();
    img.onload = () => {
      element.src = src;
      element.classList.add('loaded');
    };
    img.src = src;
  }
}

// 3. 图片压缩和转换
const sharp = require('sharp');

async function optimizeImage(inputPath, outputPath, options = {}) {
  const {
    width,
    height,
    quality = 80,
    format = 'webp'
  } = options;
  
  try {
    let pipeline = sharp(inputPath);
    
    // 调整尺寸
    if (width || height) {
      pipeline = pipeline.resize(width, height, {
        fit: 'inside',
        withoutEnlargement: true
      });
    }
    
    // 根据格式转换
    switch (format) {
      case 'webp':
        pipeline = pipeline.webp({ quality });
        break;
      case 'avif':
        pipeline = pipeline.avif({ quality });
        break;
      case 'jpeg':
        pipeline = pipeline.jpeg({ quality });
        break;
      default:
        pipeline = pipeline.webp({ quality });
    }
    
    await pipeline.toFile(outputPath);
    console.log(`Image optimized: ${outputPath}`);
  } catch (error) {
    console.error('Image optimization failed:', error);
  }
}

// 4. 图片加载优化
class ImageLoader {
  constructor() {
    this.loadingQueue = [];
    this.maxConcurrent = 3;
    this.currentLoading = 0;
  }
  
  load(url, priority = 'normal') {
    return new Promise((resolve, reject) => {
      this.loadingQueue.push({
        url,
        priority,
        resolve,
        reject
      });
      
      this.processQueue();
    });
  }
  
  processQueue() {
    if (this.currentLoading >= this.maxConcurrent) return;
    
    // 按优先级排序
    this.loadingQueue.sort((a, b) => {
      const priorityOrder = { high: 0, normal: 1, low: 2 };
      return priorityOrder[a.priority] - priorityOrder[b.priority];
    });
    
    const next = this.loadingQueue.shift();
    if (!next) return;
    
    this.currentLoading++;
    
    const img = new Image();
    img.onload = () => {
      this.currentLoading--;
      next.resolve(img);
      this.processQueue();
    };
    
    img.onerror = () => {
      this.currentLoading--;
      next.reject(new Error(`Failed to load image: ${next.url}`));
      this.processQueue();
    };
    
    img.src = next.url;
  }
}

// 5. 图片预加载策略
class ImagePreloader {
  constructor() {
    this.cache = new Map();
    this.loader = new ImageLoader();
  }
  
  preload(urls, priority = 'low') {
    return Promise.all(
      urls.map(url => {
        if (this.cache.has(url)) {
          return Promise.resolve(this.cache.get(url));
        }
        
        return this.loader.load(url, priority).then(img => {
          this.cache.set(url, img);
          return img;
        });
      })
    );
  }
  
  // 预测用户可能查看的图片
  predictNextImages(currentPath) {
    const predictions = {
      '/': ['/images/hero.webp', '/images/featured-1.webp'],
      '/products': ['/images/product-list.webp'],
      '/about': ['/images/team.webp']
    };
    
    return predictions[currentPath] || [];
  }
}

// 使用示例
const preloader = new ImagePreloader();

// 预加载当前页面图片
document.addEventListener('DOMContentLoaded', () => {
  const currentImages = Array.from(document.querySelectorAll('img[data-src]'))
    .map(img => img.dataset.src);
  
  preloader.preload(currentImages, 'high');
  
  // 预加载可能的下一个页面图片
  const nextImages = preloader.predictNextImages(window.location.pathname);
  preloader.preload(nextImages, 'low');
});

🧠 第五难:JavaScript性能 - 内存管理的"收心法"

问题:如何避免内存泄漏?如何优化JavaScript执行性能?大型应用如何保持流畅运行?

深度技术:

JavaScript性能优化涉及多个层面:内存管理、执行效率、代码分割等。理解V8引擎的工作原理、垃圾回收机制,以及如何编写高性能的JavaScript代码,是提升应用性能的关键。

代码示例:

// 1. 内存泄漏检测
class MemoryLeakDetector {
  constructor() {
    this.snapshots = [];
    this.interval = null;
  }
  
  start(intervalMs = 10000) {
    this.interval = setInterval(() => {
      this.takeSnapshot();
    }, intervalMs);
  }
  
  stop() {
    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
    }
  }
  
  takeSnapshot() {
    const snapshot = {
      timestamp: Date.now(),
      heapSize: performance.memory?.usedJSHeapSize,
      domNodes: document.getElementsByTagName('*').length,
      eventListeners: this.countEventListeners()
    };
    
    this.snapshots.push(snapshot);
    this.analyzeSnapshots();
  }
  
  countEventListeners() {
    // 获取所有元素的事件监听器数量
    const elements = document.getElementsByTagName('*');
    let count = 0;
    
    for (const element of elements) {
      const listeners = getEventListeners(element);
      count += Object.keys(listeners).length;
    }
    
    return count;
  }
  
  analyzeSnapshots() {
    if (this.snapshots.length < 2) return;
    
    const current = this.snapshots[this.snapshots.length - 1];
    const previous = this.snapshots[this.snapshots.length - 2];
    
    const heapGrowth = current.heapSize - previous.heapSize;
    const domGrowth = current.domNodes - previous.domNodes;
    const listenerGrowth = current.eventListeners - previous.eventListeners;
    
    if (heapGrowth > 1000000 || domGrowth > 100 || listenerGrowth > 50) {
      console.warn('Potential memory leak detected:', {
        heapGrowth: `${(heapGrowth / 1024 / 1024).toFixed(2)}MB`,
        domGrowth,
        listenerGrowth
      });
    }
  }
}

// 2. 性能优化工具类
class PerformanceOptimizer {
  // 防抖函数
  debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }
  
  // 节流函数
  throttle(func, limit) {
    let inThrottle;
    return function executedFunction(...args) {
      if (!inThrottle) {
        func(...args);
        inThrottle = true;
        setTimeout(() => inThrottle = false, limit);
      }
    };
  }
  
  // 批量处理
  batchProcess(items, processFn, batchSize = 100) {
    return new Promise((resolve) => {
      let index = 0;
      
      function processBatch() {
        const batch = items.slice(index, index + batchSize);
        if (batch.length === 0) {
          resolve();
          return;
        }
        
        Promise.all(batch.map(processFn))
          .then(() => {
            index += batchSize;
            setTimeout(processBatch, 0);
          });
      }
      
      processBatch();
    });
  }
  
  // 虚拟列表实现
  createVirtualList(container, items, itemHeight, renderItem) {
    const visibleItems = Math.ceil(container.clientHeight / itemHeight);
    const totalHeight = items.length * itemHeight;
    
    container.style.position = 'relative';
    container.style.height = `${totalHeight}px`;
    
    const visibleRange = {
      start: 0,
      end: visibleItems
    };
    
    function updateVisibleItems() {
      const scrollTop = container.scrollTop;
      const start = Math.floor(scrollTop / itemHeight);
      const end = Math.min(start + visibleItems + 1, items.length);
      
      if (start !== visibleRange.start || end !== visibleRange.end) {
        visibleRange.start = start;
        visibleRange.end = end;
        
        // 清除现有内容
        container.innerHTML = '';
        
        // 渲染可见项
        for (let i = start; i < end; i++) {
          const item = renderItem(items[i], i);
          item.style.position = 'absolute';
          item.style.top = `${i * itemHeight}px`;
          item.style.height = `${itemHeight}px`;
          container.appendChild(item);
        }
      }
    }
    
    container.addEventListener('scroll', this.throttle(updateVisibleItems, 16));
    updateVisibleItems();
  }
}

// 3. 代码分割和懒加载
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: 10,
      maxAsyncRequests: 10,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

// 动态导入示例
async function loadModule(moduleName) {
  try {
    const module = await import(`./modules/${moduleName}.js`);
    return module;
  } catch (error) {
    console.error(`Failed to load module: ${moduleName}`, error);
    return null;
  }
}

// 4. 性能监控
class PerformanceMonitor {
  constructor() {
    this.metrics = {};
    this.observers = new Map();
  }
  
  startMonitoring() {
    // 监控长任务
    this.observeLongTasks();
    
    // 监控资源加载
    this.observeResourceTiming();
    
    // 监控布局变化
    this.observeLayoutShifts();
    
    // 监控内存使用
    this.observeMemoryUsage();
  }
  
  observeLongTasks() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.duration > 50) {
          console.warn('Long task detected:', entry);
        }
      }
    });
    
    observer.observe({ entryTypes: ['longtask'] });
    this.observers.set('longtask', observer);
  }
  
  observeResourceTiming() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.initiatorType === 'script' && entry.duration > 1000) {
          console.warn('Slow script loading:', entry);
        }
      }
    });
    
    observer.observe({ entryTypes: ['resource'] });
    this.observers.set('resource', observer);
  }
  
  observeLayoutShifts() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.value > 0.1) {
          console.warn('Layout shift detected:', entry);
        }
      }
    });
    
    observer.observe({ entryTypes: ['layout-shift'] });
    this.observers.set('layout-shift', observer);
  }
  
  observeMemoryUsage() {
    if (performance.memory) {
      setInterval(() => {
        const used = performance.memory.usedJSHeapSize;
        const total = performance.memory.totalJSHeapSize;
        const limit = performance.memory.jsHeapSizeLimit;
        
        if (used / limit > 0.9) {
          console.warn('High memory usage detected:', {
            used: `${(used / 1024 / 1024).toFixed(2)}MB`,
            total: `${(total / 1024 / 1024).toFixed(2)}MB`,
            limit: `${(limit / 1024 / 1024).toFixed(2)}MB`
          });
        }
      }, 5000);
    }
  }
  
  stopMonitoring() {
    for (const [type, observer] of this.observers) {
      observer.disconnect();
    }
    this.observers.clear();
  }
}

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

// 启动性能监控
monitor.startMonitoring();

// 使用防抖处理搜索输入
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', optimizer.debounce((e) => {
  // 处理搜索逻辑
}, 300));

// 使用节流处理滚动事件
window.addEventListener('scroll', optimizer.throttle(() => {
  // 处理滚动逻辑
}, 16));

// 批量处理大量数据
const items = Array.from({ length: 10000 }, (_, i) => i);
optimizer.batchProcess(items, async (item) => {
  // 处理单个项目
}, 100);

🎨 第六难:CSS性能 - 选择器与动画的"抱火诀"

问题:如何优化CSS选择器性能?如何实现高性能的动画效果?如何避免重排重绘?

深度技术:

CSS性能优化涉及选择器效率、动画性能、渲染优化等多个方面。理解浏览器渲染原理、CSS选择器匹配规则,以及如何编写高性能的CSS代码,是提升页面渲染性能的关键。

代码示例:

/* 1. 高性能选择器 */
/* 避免使用通配符选择器 */
* { margin: 0; padding: 0; } /* 不推荐 */

/* 避免使用标签选择器 */
div { margin: 10px; } /* 不推荐 */

/* 使用类选择器 */
.container { margin: 10px; } /* 推荐 */

/* 避免过度嵌套 */
.nav ul li a { color: blue; } /* 不推荐 */
.nav-link { color: blue; } /* 推荐 */

/* 2. 高性能动画 */
/* 使用transform和opacity进行动画 */
.animate {
  /* 不推荐 */
  left: 0;
  transition: left 0.3s ease;
}

.animate {
  /* 推荐 */
  transform: translateX(0);
  transition: transform 0.3s ease;
}

/* 使用will-change提示浏览器 */
.animate {
  will-change: transform;
  transform: translateX(0);
  transition: transform 0.3s ease;
}

/* 3. 避免重排重绘 */
/* 批量修改DOM */
function batchUpdate() {
  // 不推荐
  element.style.width = '100px';
  element.style.height = '100px';
  element.style.margin = '10px';
  
  // 推荐
  element.style.cssText = `
    width: 100px;
    height: 100px;
    margin: 10px;
  `;
}

/* 使用DocumentFragment */
function createList(items) {
  const fragment = document.createDocumentFragment();
  
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    fragment.appendChild(li);
  });
  
  document.querySelector('ul').appendChild(fragment);
}

/* 4. CSS性能优化工具 */
class CSSOptimizer {
  constructor() {
    this.rules = new Map();
  }
  
  // 分析选择器性能
  analyzeSelector(selector) {
    const complexity = this.calculateSelectorComplexity(selector);
    const specificity = this.calculateSpecificity(selector);
    
    return {
      complexity,
      specificity,
      performance: this.evaluatePerformance(complexity, specificity)
    };
  }
  
  calculateSelectorComplexity(selector) {
    // 计算选择器复杂度
    const parts = selector.split(/\s+/);
    return parts.length;
  }
  
  calculateSpecificity(selector) {
    // 计算选择器特异性
    let a = 0, b = 0, c = 0;
    
    // ID选择器
    a = (selector.match(/#/g) || []).length;
    
    // 类选择器、属性选择器、伪类
    b = (selector.match(/\.|\[|:/g) || []).length;
    
    // 元素选择器、伪元素
    c = (selector.match(/^[a-zA-Z]|::/g) || []).length;
    
    return { a, b, c };
  }
  
  evaluatePerformance(complexity, specificity) {
    // 评估选择器性能
    const score = complexity * 0.4 + (specificity.a + specificity.b + specificity.c) * 0.6;
    
    if (score < 2) return 'excellent';
    if (score < 4) return 'good';
    if (score < 6) return 'fair';
    return 'poor';
  }
  
  // 优化动画性能
  optimizeAnimation(element, properties) {
    const optimized = {};
    
    for (const [prop, value] of Object.entries(properties)) {
      if (prop === 'transform' || prop === 'opacity') {
        optimized[prop] = value;
      } else {
        console.warn(`Avoid animating ${prop}, use transform instead`);
      }
    }
    
    return optimized;
  }
  
  // 检测重排重绘
  detectReflow(element) {
    const originalStyle = window.getComputedStyle(element);
    
    return {
      before: () => {
        this.originalLayout = element.getBoundingClientRect();
      },
      after: () => {
        const newLayout = element.getBoundingClientRect();
        const newStyle = window.getComputedStyle(element);
        
        const hasReflow = this.originalLayout.width !== newLayout.width ||
                         this.originalLayout.height !== newLayout.height;
        
        const hasRepaint = originalStyle.color !== newStyle.color ||
                          originalStyle.backgroundColor !== newStyle.backgroundColor;
        
        return { hasReflow, hasRepaint };
      }
    };
  }
}

// 使用示例
const optimizer = new CSSOptimizer();

// 分析选择器性能
const selectorAnalysis = optimizer.analyzeSelector('.nav > ul > li > a');
console.log('Selector performance:', selectorAnalysis);

// 优化动画
const animation = optimizer.optimizeAnimation(element, {
  transform: 'translateX(100px)',
  opacity: 0.5,
  width: '200px' // 不推荐
});

// 检测重排重绘
const reflowDetector = optimizer.detectReflow(element);
reflowDetector.before();
// 执行可能引起重排重绘的操作
const { hasReflow, hasRepaint } = reflowDetector.after();
if (hasReflow) console.warn('Layout reflow detected');
if (hasRepaint) console.warn('Repaint detected');

📝 第七难:字体加载 - FOUT与FOIT的"隐身术"

问题:如何优化字体加载体验?如何避免字体闪烁?如何实现平滑的字体切换?

深度技术:

字体加载优化是提升用户体验的重要环节。理解FOUT(Flash of Unstyled Text)和FOIT(Flash of Invisible Text)的区别,以及如何控制字体加载行为,是优化字体显示效果的关键。

代码示例:

// 1. 字体加载策略
class FontLoader {
  constructor(options = {}) {
    this.options = {
      display: 'swap',
      timeout: 3000,
      ...options
    };
    
    this.loadedFonts = new Set();
  }
  
  // 加载字体
  loadFont(fontFamily, urls) {
    if (this.loadedFonts.has(fontFamily)) {
      return Promise.resolve();
    }
    
    const fontFaceSet = new FontFaceSet();
    const fontFaces = urls.map(url => {
      const format = url.split('.').pop();
      return new FontFace(fontFamily, `url(${url})`, {
        style: 'normal',
        weight: '400',
        display: this.options.display
      });
    });
    
    return Promise.all(fontFaces.map(face => face.load()))
      .then(loadedFaces => {
        loadedFaces.forEach(face => {
          document.fonts.add(face);
        });
        this.loadedFonts.add(fontFamily);
      })
      .catch(error => {
        console.error(`Failed to load font ${fontFamily}:`, error);
      });
  }
  
  // 预加载字体
  preloadFonts(fonts) {
    const links = fonts.map(font => {
      const link = document.createElement('link');
      link.rel = 'preload';
      link.href = font.url;
      link.as = 'font';
      link.crossOrigin = 'anonymous';
      return link;
    });
    
    links.forEach(link => document.head.appendChild(link));
  }
  
  // 监控字体加载
  observeFontLoading() {
    const observer = new FontFaceObserver('Custom Font');
    
    observer.load(null, this.options.timeout)
      .then(() => {
        document.documentElement.classList.add('fonts-loaded');
      })
      .catch(() => {
        document.documentElement.classList.add('fonts-failed');
      });
  }
}

// 2. 字体显示控制
class FontDisplayController {
  constructor() {
    this.fontDisplay = new Map();
  }
  
  // 设置字体显示策略
  setFontDisplay(fontFamily, display) {
    this.fontDisplay.set(fontFamily, display);
    
    const style = document.createElement('style');
    style.textContent = `
      @font-face {
        font-family: ${fontFamily};
        font-display: ${display};
      }
    `;
    
    document.head.appendChild(style);
  }
  
  // 获取字体加载状态
  getFontStatus(fontFamily) {
    return document.fonts.check(`12px "${fontFamily}"`);
  }
  
  // 监听字体加载完成
  onFontLoaded(fontFamily, callback) {
    if (this.getFontStatus(fontFamily)) {
      callback();
      return;
    }
    
    document.fonts.ready.then(() => {
      if (this.getFontStatus(fontFamily)) {
        callback();
      }
    });
  }
}

// 3. 字体回退策略
class FontFallback {
  constructor() {
    this.fallbacks = new Map();
  }
  
  // 设置字体回退链
  setFallback(fontFamily, fallbackChain) {
    this.fallbacks.set(fontFamily, fallbackChain);
    
    const style = document.createElement('style');
    style.textContent = `
      .${fontFamily} {
        font-family: ${fallbackChain.join(', ')};
      }
    `;
    
    document.head.appendChild(style);
  }
  
  // 应用字体回退
  applyFallback(element, fontFamily) {
    const fallbackChain = this.fallbacks.get(fontFamily);
    if (fallbackChain) {
      element.style.fontFamily = fallbackChain.join(', ');
    }
  }
}

// 使用示例
const fontLoader = new FontLoader({
  display: 'swap',
  timeout: 3000
});

const displayController = new FontDisplayController();
const fontFallback = new FontFallback();

// 加载自定义字体
fontLoader.loadFont('Custom Font', [
  '/fonts/custom-font.woff2',
  '/fonts/custom-font.woff'
]);

// 设置字体显示策略
displayController.setFontDisplay('Custom Font', 'swap');

// 设置字体回退
fontFallback.setFallback('Custom Font', [
  'Custom Font',
  'Arial',
  'sans-serif'
]);

// 监听字体加载
displayController.onFontLoaded('Custom Font', () => {
  document.body.classList.add('custom-font-loaded');
});

// 预加载字体
fontLoader.preloadFonts([
  { url: '/fonts/custom-font.woff2' }
]);

// 监控字体加载
fontLoader.observeFontLoading();

🔮 第八难:预加载技术 - Prefetch与Preload的"千里眼"

问题:如何预测用户行为并预加载资源?如何平衡预加载与性能消耗?如何实现智能预加载策略?

深度技术:

预加载技术是提升用户体验的重要手段,通过预测用户行为并提前加载资源,可以显著减少等待时间。理解不同预加载策略的特点和适用场景,是优化资源加载效率的关键。

代码示例:

// 1. 预加载管理器
class PreloadManager {
  constructor() {
    this.prefetchQueue = new Set();
    this.preloadQueue = new Set();
    this.maxConcurrent = 3;
    this.currentLoading = 0;
  }
  
  // 预获取资源
  prefetch(url, options = {}) {
    if (this.prefetchQueue.has(url)) return;
    
    this.prefetchQueue.add(url);
    this.processPrefetchQueue();
  }
  
  // 预加载资源
  preload(url, options = {}) {
    if (this.preloadQueue.has(url)) return;
    
    this.preloadQueue.add(url);
    this.processPreloadQueue();
  }
  
  // 处理预获取队列
  processPrefetchQueue() {
    if (this.currentLoading >= this.maxConcurrent) return;
    
    const url = Array.from(this.prefetchQueue)[0];
    if (!url) return;
    
    this.prefetchQueue.delete(url);
    this.currentLoading++;
    
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = url;
    link.onload = () => {
      this.currentLoading--;
      this.processPrefetchQueue();
    };
    link.onerror = () => {
      this.currentLoading--;
      this.processPrefetchQueue();
    };
    
    document.head.appendChild(link);
  }
  
  // 处理预加载队列
  processPreloadQueue() {
    if (this.currentLoading >= this.maxConcurrent) return;
    
    const url = Array.from(this.preloadQueue)[0];
    if (!url) return;
    
    this.preloadQueue.delete(url);
    this.currentLoading++;
    
    const link = document.createElement('link');
    link.rel = 'preload';
    link.href = url;
    link.as = this.getResourceType(url);
    link.onload = () => {
      this.currentLoading--;
      this.processPreloadQueue();
    };
    link.onerror = () => {
      this.currentLoading--;
      this.processPreloadQueue();
    };
    
    document.head.appendChild(link);
  }
  
  // 获取资源类型
  getResourceType(url) {
    const extension = url.split('.').pop().toLowerCase();
    
    switch (extension) {
      case 'js':
        return 'script';
      case 'css':
        return 'style';
      case 'jpg':
      case 'jpeg':
      case 'png':
      case 'gif':
      case 'webp':
        return 'image';
      case 'woff':
      case 'woff2':
      case 'ttf':
      case 'otf':
        return 'font';
      default:
        return 'fetch';
    }
  }
}

// 2. 智能预加载
class SmartPreloader {
  constructor() {
    this.preloadManager = new PreloadManager();
    this.userBehavior = new Map();
    this.threshold = 0.7;
  }
  
  // 记录用户行为
  recordBehavior(action, target) {
    const key = `${action}-${target}`;
    this.userBehavior.set(key, (this.userBehavior.get(key) || 0) + 1);
  }
  
  // 预测下一个动作
  predictNextAction(currentAction) {
    const predictions = new Map();
    
    for (const [key, count] of this.userBehavior) {
      const [action, target] = key.split('-');
      if (action === currentAction) {
        predictions.set(target, count);
      }
    }
    
    return Array.from(predictions.entries())
      .filter(([_, count]) => count > this.threshold)
      .sort((a, b) => b[1] - a[1])
      .map(([target]) => target);
  }
  
  // 预加载预测资源
  preloadPredictedResources(currentAction) {
    const predictedTargets = this.predictNextAction(currentAction);
    
    predictedTargets.forEach(target => {
      const resources = this.getResourcesForTarget(target);
      resources.forEach(resource => {
        this.preloadManager.preload(resource);
      });
    });
  }
  
  // 获取目标相关资源
  getResourcesForTarget(target) {
    // 根据目标返回需要预加载的资源
    const resourceMap = {
      'home': ['/styles/home.css', '/scripts/home.js'],
      'products': ['/styles/products.css', '/scripts/products.js'],
      'about': ['/styles/about.css', '/scripts/about.js']
    };
    
    return resourceMap[target] || [];
  }
}

// 3. 路由预加载
class RoutePreloader {
  constructor() {
    this.preloadManager = new PreloadManager();
    this.routes = new Map();
  }
  
  // 注册路由
  registerRoute(path, resources) {
    this.routes.set(path, resources);
  }
  
  // 预加载路由资源
  preloadRoute(path) {
    const resources = this.routes.get(path);
    if (resources) {
      resources.forEach(resource => {
        this.preloadManager.preload(resource);
      });
    }
  }
  
  // 监听路由变化
  observeRouteChanges() {
    let lastPath = window.location.pathname;
    
    // 监听点击事件
    document.addEventListener('click', (event) => {
      const link = event.target.closest('a');
      if (link && link.href.startsWith(window.location.origin)) {
        const path = new URL(link.href).pathname;
        this.preloadRoute(path);
      }
    });
    
    // 监听路由变化
    window.addEventListener('popstate', () => {
      const currentPath = window.location.pathname;
      if (currentPath !== lastPath) {
        this.preloadRoute(currentPath);
        lastPath = currentPath;
      }
    });
  }
}

// 使用示例
const preloadManager = new PreloadManager();
const smartPreloader = new SmartPreloader();
const routePreloader = new RoutePreloader();

// 预获取资源
preloadManager.prefetch('/images/hero.jpg');
preloadManager.prefetch('/styles/main.css');

// 预加载资源
preloadManager.preload('/scripts/main.js');
preloadManager.preload('/fonts/custom-font.woff2');

// 记录用户行为
smartPreloader.recordBehavior('click', 'products');
smartPreloader.recordBehavior('click', 'about');

// 预加载预测资源
smartPreloader.preloadPredictedResources('click');

// 注册路由
routePreloader.registerRoute('/products', [
  '/styles/products.css',
  '/scripts/products.js'
]);

// 监听路由变化
routePreloader.observeRouteChanges();

📊 第九难:性能监控 - 前端埋点的"顺风耳"

问题:如何全面监控前端性能?如何收集和分析性能数据?如何建立性能监控体系?

深度技术:

性能监控是优化前端性能的基础,通过收集和分析性能数据,可以发现问题并持续改进。理解性能指标、监控方法和数据分析技术,是建立完整性能监控体系的关键。

代码示例:

// 1. 性能监控器
class PerformanceMonitor {
  constructor() {
    this.metrics = {};
    this.observers = new Map();
  }
  
  // 监控核心性能指标
  monitorCoreWebVitals() {
    // 监控LCP (Largest Contentful Paint)
    this.observeLCP();
    
    // 监控FID (First Input Delay)
    this.observeFID();
    
    // 监控CLS (Cumulative Layout Shift)
    this.observeCLS();
  }
  
  // 监控LCP
  observeLCP() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        this.metrics.lcp = entry.startTime;
        this.reportMetric('lcp', entry.startTime);
      }
    });
    
    observer.observe({ entryTypes: ['largest-contentful-paint'] });
    this.observers.set('lcp', observer);
  }
  
  // 监控FID
  observeFID() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        this.metrics.fid = entry.processingStart - entry.startTime;
        this.reportMetric('fid', this.metrics.fid);
      }
    });
    
    observer.observe({ entryTypes: ['first-input'] });
    this.observers.set('fid', observer);
  }
  
  // 监控CLS
  observeCLS() {
    let clsValue = 0;
    let clsEntries = [];
    
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          clsValue += entry.value;
          clsEntries.push(entry);
        }
      }
      
      this.metrics.cls = clsValue;
      this.reportMetric('cls', clsValue);
    });
    
    observer.observe({ entryTypes: ['layout-shift'] });
    this.observers.set('cls', observer);
  }
  
  // 监控资源加载
  monitorResourceLoading() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.initiatorType === 'script' && entry.duration > 1000) {
          this.reportMetric('slow-script', {
            url: entry.name,
            duration: entry.duration
          });
        }
      }
    });
    
    observer.observe({ entryTypes: ['resource'] });
    this.observers.set('resource', observer);
  }
  
  // 监控长任务
  monitorLongTasks() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.duration > 50) {
          this.reportMetric('long-task', {
            duration: entry.duration,
            startTime: entry.startTime
          });
        }
      }
    });
    
    observer.observe({ entryTypes: ['longtask'] });
    this.observers.set('longtask', observer);
  }
  
  // 监控内存使用
  monitorMemoryUsage() {
    if (performance.memory) {
      setInterval(() => {
        const used = performance.memory.usedJSHeapSize;
        const total = performance.memory.totalJSHeapSize;
        const limit = performance.memory.jsHeapSizeLimit;
        
        this.reportMetric('memory', {
          used,
          total,
          limit
        });
      }, 5000);
    }
  }
  
  // 报告性能指标
  reportMetric(name, value) {
    // 发送到性能监控服务
    console.log(`Metric: ${name}`, value);
    
    // 可以发送到后端API
    // fetch('/api/metrics', {
    //   method: 'POST',
    //   body: JSON.stringify({ name, value })
    // });
  }
  
  // 停止监控
  stopMonitoring() {
    for (const [type, observer] of this.observers) {
      observer.disconnect();
    }
    this.observers.clear();
  }
}

// 2. 性能数据收集器
class PerformanceCollector {
  constructor() {
    this.data = new Map();
  }
  
  // 收集性能指标
  collectMetrics() {
    // 收集导航计时
    const navigation = performance.getEntriesByType('navigation')[0];
    this.data.set('navigation', {
      dns: navigation.domainLookupEnd - navigation.domainLookupStart,
      tcp: navigation.connectEnd - navigation.connectStart,
      request: navigation.responseEnd - navigation.requestStart,
      dom: navigation.domComplete - navigation.domLoading,
      load: navigation.loadEventEnd - navigation.loadEventStart
    });
    
    // 收集资源计时
    const resources = performance.getEntriesByType('resource');
    this.data.set('resources', resources.map(resource => ({
      url: resource.name,
      type: resource.initiatorType,
      duration: resource.duration,
      size: resource.transferSize
    })));
    
    // 收集用户计时
    const userTiming = performance.getEntriesByType('measure');
    this.data.set('userTiming', userTiming.map(measure => ({
      name: measure.name,
      duration: measure.duration
    })));
  }
  
  // 收集错误信息
  collectErrors() {
    const errors = [];
    
    window.addEventListener('error', (event) => {
      errors.push({
        type: 'error',
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        stack: event.error?.stack
      });
    });
    
    window.addEventListener('unhandledrejection', (event) => {
      errors.push({
        type: 'unhandledrejection',
        message: event.reason?.message || event.reason,
        stack: event.reason?.stack
      });
    });
    
    this.data.set('errors', errors);
  }
  
  // 收集用户行为
  collectUserBehavior() {
    const behavior = [];
    
    // 记录点击事件
    document.addEventListener('click', (event) => {
      behavior.push({
        type: 'click',
        target: event.target.tagName,
        timestamp: Date.now()
      });
    });
    
    // 记录滚动事件
    let scrollTimeout;
    window.addEventListener('scroll', () => {
      clearTimeout(scrollTimeout);
      scrollTimeout = setTimeout(() => {
        behavior.push({
          type: 'scroll',
          position: window.scrollY,
          timestamp: Date.now()
        });
      }, 100);
    });
    
    this.data.set('behavior', behavior);
  }
  
  // 获取收集的数据
  getData() {
    return Object.fromEntries(this.data);
  }
}

// 3. 性能分析器
class PerformanceAnalyzer {
  constructor(data) {
    this.data = data;
  }
  
  // 分析性能问题
  analyze() {
    const issues = [];
    
    // 分析加载时间
    const navigation = this.data.navigation;
    if (navigation.dom > 1000) {
      issues.push({
        type: 'slow-dom',
        message: 'DOM加载时间过长',
        value: navigation.dom
      });
    }
    
    // 分析资源加载
    const resources = this.data.resources;
    const slowResources = resources.filter(r => r.duration > 1000);
    if (slowResources.length > 0) {
      issues.push({
        type: 'slow-resources',
        message: '存在慢资源加载',
        resources: slowResources
      });
    }
    
    // 分析错误
    const errors = this.data.errors;
    if (errors.length > 0) {
      issues.push({
        type: 'errors',
        message: '存在未处理的错误',
        errors
      });
    }
    
    return issues;
  }
  
  // 生成性能报告
  generateReport() {
    const issues = this.analyze();
    
    return {
      timestamp: Date.now(),
      metrics: this.data,
      issues,
      recommendations: this.generateRecommendations(issues)
    };
  }
  
  // 生成优化建议
  generateRecommendations(issues) {
    const recommendations = [];
    
    issues.forEach(issue => {
      switch (issue.type) {
        case 'slow-dom':
          recommendations.push({
            type: 'dom',
            message: '考虑优化DOM结构,减少DOM节点数量'
          });
          break;
        case 'slow-resources':
          recommendations.push({
            type: 'resources',
            message: '考虑使用CDN加速资源加载,或优化资源大小'
          });
          break;
        case 'errors':
          recommendations.push({
            type: 'errors',
            message: '建议添加错误监控和自动上报机制'
          });
          break;
      }
    });
    
    return recommendations;
  }
}

// 使用示例
const monitor = new PerformanceMonitor();
const collector = new PerformanceCollector();
const analyzer = new PerformanceAnalyzer(collector.getData());

// 启动性能监控
monitor.monitorCoreWebVitals();
monitor.monitorResourceLoading();
monitor.monitorLongTasks();
monitor.monitorMemoryUsage();

// 收集性能数据
collector.collectMetrics();
collector.collectErrors();
collector.collectUserBehavior();

// 分析性能问题
const report = analyzer.generateReport();
console.log('Performance Report:', report);

// 停止监控
monitor.stopMonitoring();

结语

通过这九大性能优化心经,我们从前端性能的各个维度进行了深入探讨。从网络传输到渲染优化,从资源加载到性能监控,每个环节都蕴含着提升用户体验的关键技术。希望这些实战技巧能帮助你在前端性能优化的道路上走得更远,打造出真正流畅、高效的用户体验。

记住,性能优化不是一蹴而就的,而是需要持续关注和改进的过程。让我们继续在前端性能优化的道路上探索前行,为用户创造更好的体验。


网站公告

今日签到

点亮在社区的每一天
去签到