2026-01-19 09:34:13 +08:00

6.0 KiB

性能与优化

1. 图片懒加载

IntersectionObserver 方案(推荐)

function lazyLoadImages() {
  const observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.classList.remove('lazy');
        observer.unobserve(img);
      }
    });
  }, {
    rootMargin: '50px' // 提前50px开始加载
  });

  document.querySelectorAll('img.lazy').forEach(img => observer.observe(img));
}

// HTML: <img class="lazy" data-src="real-image.jpg" src="placeholder.jpg" />

传统滚动监听方案

function lazyLoadScroll() {
  const lazyImages = document.querySelectorAll('img.lazy');
  
  const loadImage = throttle(() => {
    lazyImages.forEach(img => {
      if (img.getBoundingClientRect().top < window.innerHeight + 100) {
        img.src = img.dataset.src;
        img.classList.remove('lazy');
      }
    });
  }, 200);

  window.addEventListener('scroll', loadImage);
  loadImage(); // 初始加载
}

2. 虚拟列表(大数据渲染优化)

class VirtualList {
  constructor({ container, itemHeight, totalItems, renderItem }) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.totalItems = totalItems;
    this.renderItem = renderItem;
    
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
    this.startIndex = 0;
    
    this.init();
  }
  
  init() {
    // 创建容器结构
    this.container.style.overflow = 'auto';
    this.container.style.position = 'relative';
    
    // 占位元素,撑开滚动高度
    this.phantom = document.createElement('div');
    this.phantom.style.height = `${this.totalItems * this.itemHeight}px`;
    
    // 实际渲染的列表容器
    this.content = document.createElement('div');
    this.content.style.position = 'absolute';
    this.content.style.top = '0';
    this.content.style.width = '100%';
    
    this.container.appendChild(this.phantom);
    this.container.appendChild(this.content);
    
    this.container.addEventListener('scroll', throttle(() => this.onScroll(), 16));
    this.render();
  }
  
  onScroll() {
    const scrollTop = this.container.scrollTop;
    this.startIndex = Math.floor(scrollTop / this.itemHeight);
    this.content.style.transform = `translateY(${this.startIndex * this.itemHeight}px)`;
    this.render();
  }
  
  render() {
    const fragment = document.createDocumentFragment();
    const endIndex = Math.min(this.startIndex + this.visibleCount, this.totalItems);
    
    for (let i = this.startIndex; i < endIndex; i++) {
      const item = this.renderItem(i);
      item.style.height = `${this.itemHeight}px`;
      fragment.appendChild(item);
    }
    
    this.content.innerHTML = '';
    this.content.appendChild(fragment);
  }
}

// 使用示例
new VirtualList({
  container: document.getElementById('list'),
  itemHeight: 50,
  totalItems: 10000,
  renderItem: (index) => {
    const div = document.createElement('div');
    div.textContent = `Item ${index}`;
    return div;
  }
});

3. 函数节流在滚动事件中的应用

// 节流函数
function throttle(fn, delay) {
  let lastTime = 0;
  let timer = null;
  
  return function(...args) {
    const now = Date.now();
    const remaining = delay - (now - lastTime);
    
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      lastTime = now;
      fn.apply(this, args);
    } else if (!timer) {
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = null;
        fn.apply(this, args);
      }, remaining);
    }
  };
}

// 应用场景
window.addEventListener('scroll', throttle(() => {
  console.log('滚动位置:', window.scrollY);
  // 吸顶效果、返回顶部按钮显示等
}, 100));

window.addEventListener('resize', throttle(() => {
  console.log('窗口大小:', window.innerWidth, window.innerHeight);
}, 200));

4. requestAnimationFrame 优化动画

// 使用 rAF 实现平滑滚动
function smoothScrollTo(target, duration = 500) {
  const start = window.scrollY;
  const distance = target - start;
  const startTime = performance.now();
  
  function step(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    
    // 缓动函数
    const easeProgress = 1 - Math.pow(1 - progress, 3);
    window.scrollTo(0, start + distance * easeProgress);
    
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }
  
  requestAnimationFrame(step);
}

// rAF 节流
function rafThrottle(fn) {
  let ticking = false;
  return function(...args) {
    if (!ticking) {
      requestAnimationFrame(() => {
        fn.apply(this, args);
        ticking = false;
      });
      ticking = true;
    }
  };
}

5. Web Worker 处理耗时任务

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ type: 'heavyTask', data: largeArray });

worker.onmessage = (e) => {
  console.log('处理结果:', e.data);
};

// worker.js
self.onmessage = (e) => {
  if (e.data.type === 'heavyTask') {
    const result = e.data.data.map(item => /* 耗时计算 */);
    self.postMessage(result);
  }
};

6. 时间切片(避免长任务阻塞)

async function timeSlice(tasks, chunkSize = 5) {
  const results = [];
  
  for (let i = 0; i < tasks.length; i += chunkSize) {
    const chunk = tasks.slice(i, i + chunkSize);
    
    // 处理一批任务
    for (const task of chunk) {
      results.push(task());
    }
    
    // 让出主线程
    await new Promise(resolve => setTimeout(resolve, 0));
    // 或使用 requestIdleCallback
  }
  
  return results;
}

// 使用 requestIdleCallback
function processInIdle(tasks) {
  let index = 0;
  
  function work(deadline) {
    while (index < tasks.length && deadline.timeRemaining() > 0) {
      tasks[index++]();
    }
    if (index < tasks.length) {
      requestIdleCallback(work);
    }
  }
  
  requestIdleCallback(work);
}