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

4 阅读2分钟

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

今日主题:事件循环底层揭秘 - 微任务、宏任务与浏览器调度黑盒

核心概念:事件循环并非简单的"先进先出"

// 你以为的顺序?
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');

// 实际输出:1, 4, 3, 2

🔬 事件循环的完整流程

┌───────────────────────────┐
│   执行一个宏任务(MacroTask) │ ← script整体、setTimeoutsetInterval、I/O
└───────────────────────────┘
              ↓
┌───────────────────────────┐
│   清空微任务队列           │ ← Promise.thenMutationObserver、queueMicrotask
│   (所有微任务,直到为空)   │   (包括新产生的微任务!)
└───────────────────────────┘
              ↓
┌───────────────────────────┐
│   是否到达渲染时机?       │ ← 约16.6ms一次
│   执行requestAnimationFrame│
│   执行重排/重绘/合成        │
└───────────────────────────┘
              ↓
┌───────────────────────────┐
│   处理宏任务队列的下一个     │
└───────────────────────────┘

💣 经典陷阱与高级应用

陷阱1:微任务中的死循环

// 💀 这会卡死页面!微任务永不结束
function infiniteMicrotask() {
  Promise.resolve().then(() => infiniteMicrotask());
}
// 宏任务永远得不到执行,页面无响应

陷阱2:setTimeout(fn, 0) 不是真正的0ms

// 实际延迟至少4ms(浏览器规范)
// 嵌套层级≥5时会强制最少4ms
let start = performance.now();
setTimeout(() => {
  console.log(performance.now() - start); // 通常是4-5ms
}, 0);

🚀 高级实战:零延迟调度器

实现一个不会阻塞渲染的任务调度器,利用微任务执行时机突破性能瓶颈:

class Scheduler {
  constructor() {
    this.queue = [];
    this.isProcessing = false;
  }
  
  // 添加任务(高优先级 - 微任务)
  addMicroTask(fn) {
    if (this.isProcessing) {
      // 如果在渲染周期内,推迟到下一个微任务批次
      queueMicrotask(() => fn());
    } else {
      Promise.resolve().then(fn);
    }
  }
  
  // 添加任务(低优先级 - 空闲时执行)
  addIdleTask(fn) {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(fn, { timeout: 50 });
    } else {
      setTimeout(fn, 1);
    }
  }
  
  // 批量处理大数据,避免卡顿
  async processLargeArray(items, chunkSize = 100) {
    let index = 0;
    
    const processChunk = () => {
      return new Promise((resolve) => {
        const end = Math.min(index + chunkSize, items.length);
        
        // 同步处理当前chunk
        for (let i = index; i < end; i++) {
          this.processItem(items[i]);
        }
        
        index = end;
        
        if (index < items.length) {
          // 让出主线程,给渲染和交互机会
          setTimeout(() => resolve(processChunk()), 0);
        } else {
          resolve();
        }
      });
    };
    
    await processChunk();
  }
  
  processItem(item) {
    // 实际处理逻辑
    console.log('Processing:', item);
  }
}

// 使用示例 - 处理10万条数据不卡顿
const scheduler = new Scheduler();
scheduler.processLargeArray([...Array(100000).keys()], 200);

🎯 今日挑战

实现一个带有优先级的任务队列,要求:

  1. 支持3个优先级:高(微任务)、中(下一宏任务)、低(requestIdleCallback)
  2. 高优先级任务可以"插队"
  3. 限制单次执行时间,避免长任务( > 50ms)
  4. 提供API手动让出主线程
参考实现(核心部分)
class PriorityTaskQueue {
  constructor() {
    this.high = [];   // 微任务队列
    mid: [],          // 下一tick宏任务
    low: [],          // 空闲时执行
    
    this.isPerformingMicrotask = false;
    
    // 启动调度循环
    this.schedule();
  }
  
  addTask(priority, fn) {
    switch(priority) {
      case 'high':
        if (this.isPerformingMicrotask) {
          // 当前正在执行微任务队列,放入队列避免递归深度爆炸
          this.high.push(fn);
        } else {
          queueMicrotask(() => this.executeWithTimeCheck(fn));
        }
        break;
      case 'medium':
        setTimeout(() => this.executeWithTimeCheck(fn), 0);
        break;
      case 'low':
        requestIdleCallback((deadline) => {
          if (deadline.timeRemaining() > 0) {
            this.executeWithTimeCheck(fn);
          } else {
            this.addTask('low', fn); // 重新调度
          }
        });
        break;
    }
  }
  
  executeWithTimeCheck(fn, maxTime = 50) {
    const start = performance.now();
    fn();
    const duration = performance.now() - start;
    
    if (duration > maxTime) {
      console.warn(`长任务警告: ${duration.toFixed(2)}ms`);
    }
  }
  
  schedule() {
    // 持续处理高优先级队列
    const processHighQueue = () => {
      this.isPerformingMicrotask = true;
      while (this.high.length) {
        const fn = this.high.shift();
        this.executeWithTimeCheck(fn);
      }
      this.isPerformingMicrotask = false;
      queueMicrotask(processHighQueue);
    };
    queueMicrotask(processHighQueue);
  }
}

📊 性能监控工具

// 监控长任务(Long Task API)
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn(`检测到长任务: ${entry.duration}ms`, entry);
    // 上报到监控系统
  }
});
observer.observe({ entryTypes: ['longtask'] });

明日预告:V8引擎的隐藏类与内联缓存 - 如何写出JIT友好的JavaScript代码

💡 延伸思考:你知道为什么for...infor...of慢吗?明天揭晓答案!