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

1 阅读5分钟

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

今日主题:Web Worker 深度应用 - 多线程前端计算与高性能数据处理

核心概念:JavaScript 的单线程不是限制,而是需要被管理的资源

Web Worker 让浏览器拥有了真正的并行计算能力,主线程负责 UI,Worker 负责计算。

🧵 Worker 类型与架构

┌─────────────────────────────────────────────────────┐
│                    主线程 (UI Thread)                 │
│  • DOM 操作    • 事件响应    • 渲染    • 用户交互     │
└─────────────────────────────────────────────────────┘
                          ↕ postMessage / onmessage
┌─────────────────────────────────────────────────────┐
│              Dedicated Worker (专用 Worker)          │
│  • 密集型计算    • 数据处理    • 算法执行            │
└─────────────────────────────────────────────────────┘
                          ↕
┌─────────────────────────────────────────────────────┐
│              Shared Worker (共享 Worker)              │
│  • 多页面/标签页通信    • 共享状态    • 数据缓存      │
└─────────────────────────────────────────────────────┘

🚀 基础 Worker 使用

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

// 发送数据
worker.postMessage({
  type: 'CALCULATE',
  payload: { data: largeArray, options: { threshold: 100 } }
});

// 接收结果
worker.onmessage = (event) => {
  console.log('Worker 返回结果:', event.data);
  updateUI(event.data);
};

// 错误处理
worker.onerror = (error) => {
  console.error('Worker 错误:', error);
  showErrorMessage(error.message);
};

// 终止 Worker
// worker.terminate();

// ============ worker.js ============
self.onmessage = function(event) {
  const { type, payload } = event.data;
  
  switch (type) {
    case 'CALCULATE':
      const result = performHeavyCalculation(payload);
      self.postMessage({ type: 'RESULT', payload: result });
      break;
      
    case 'PROGRESS':
      // 进度报告
      self.postMessage({ type: 'PROGRESS', payload: { percent: 50 } });
      break;
      
    default:
      console.warn('未知消息类型:', type);
  }
};

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

📊 高性能数据处理实战

// ============ 大数据处理 Worker ============
// data-processor.worker.js

// 1. 数据分块处理
class ChunkProcessor {
  constructor(data, chunkSize = 10000) {
    this.data = data;
    this.chunkSize = chunkSize;
    this.currentChunk = 0;
    this.results = [];
  }
  
  async processChunk(processor) {
    const totalChunks = Math.ceil(this.data.length / this.chunkSize);
    
    while (this.currentChunk < totalChunks) {
      const start = this.currentChunk * this.chunkSize;
      const end = Math.min(start + this.chunkSize, this.data.length);
      const chunk = this.data.slice(start, end);
      
      // 处理当前块
      const chunkResult = processor(chunk);
      this.results.push(chunkResult);
      
      // 报告进度
      const percent = (this.currentChunk / totalChunks) * 100;
      self.postMessage({ type: 'PROGRESS', payload: percent });
      
      this.currentChunk++;
      
      // 让出执行权,避免阻塞
      await new Promise(resolve => setTimeout(resolve, 0));
    }
    
    return this.mergeResults(this.results);
  }
  
  mergeResults(results) {
    // 合并各块结果
    if (Array.isArray(results[0])) {
      return results.flat();
    }
    if (typeof results[0] === 'number') {
      return results.reduce((a, b) => a + b, 0);
    }
    return results;
  }
}

// 2. 图像处理 Worker
class ImageProcessor {
  static grayscale(imageData) {
    const data = imageData.data;
    for (let i = 0; i < data.length; i += 4) {
      const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
      data[i] = gray;     // R
      data[i + 1] = gray; // G
      data[i + 2] = gray; // B
    }
    return imageData;
  }
  
  static blur(imageData, radius = 3) {
    const width = imageData.width;
    const height = imageData.height;
    const data = imageData.data;
    const output = new Uint8ClampedArray(data.length);
    
    const kernel = this.gaussianKernel(radius);
    const half = Math.floor(radius / 2);
    
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        let r = 0, g = 0, b = 0;
        let weightSum = 0;
        
        for (let ky = -half; ky <= half; ky++) {
          for (let kx = -half; kx <= half; kx++) {
            const px = Math.min(width - 1, Math.max(0, x + kx));
            const py = Math.min(height - 1, Math.max(0, y + ky));
            const idx = (py * width + px) * 4;
            const weight = kernel[ky + half][kx + half];
            
            r += data[idx] * weight;
            g += data[idx + 1] * weight;
            b += data[idx + 2] * weight;
            weightSum += weight;
          }
        }
        
        const outIdx = (y * width + x) * 4;
        output[outIdx] = r / weightSum;
        output[outIdx + 1] = g / weightSum;
        output[outIdx + 2] = b / weightSum;
        output[outIdx + 3] = data[outIdx + 3];
      }
    }
    
    return new ImageData(output, width, height);
  }
  
  static gaussianKernel(size) {
    const sigma = size / 3;
    const kernel = [];
    let sum = 0;
    
    for (let i = 0; i < size; i++) {
      kernel[i] = [];
      for (let j = 0; j < size; j++) {
        const x = i - Math.floor(size / 2);
        const y = j - Math.floor(size / 2);
        const value = Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
        kernel[i][j] = value;
        sum += value;
      }
    }
    
    // 归一化
    for (let i = 0; i < size; i++) {
      for (let j = 0; j < size; j++) {
        kernel[i][j] /= sum;
      }
    }
    
    return kernel;
  }
}

// 3. 排序算法 Worker
class SortingWorker {
  static async quickSort(arr, depth = 0) {
    if (arr.length <= 1) return arr;
    
    const pivot = arr[Math.floor(arr.length / 2)];
    const left = [];
    const right = [];
    
    for (let i = 0; i < arr.length; i++) {
      if (i === Math.floor(arr.length / 2)) continue;
      if (arr[i] < pivot) {
        left.push(arr[i]);
      } else {
        right.push(arr[i]);
      }
    }
    
    // 并行排序左右部分
    const [sortedLeft, sortedRight] = await Promise.all([
      this.quickSort(left, depth + 1),
      this.quickSort(right, depth + 1)
    ]);
    
    return [...sortedLeft, pivot, ...sortedRight];
  }
  
  static mergeSort(arr) {
    if (arr.length <= 1) return arr;
    
    const mid = Math.floor(arr.length / 2);
    const left = this.mergeSort(arr.slice(0, mid));
    const right = this.mergeSort(arr.slice(mid));
    
    return this.merge(left, right);
  }
  
  static merge(left, right) {
    const result = [];
    let i = 0, j = 0;
    
    while (i < left.length && j < right.length) {
      if (left[i] < right[j]) {
        result.push(left[i++]);
      } else {
        result.push(right[j++]);
      }
    }
    
    return result.concat(left.slice(i)).concat(right.slice(j));
  }
}

// 4. 主 Worker 入口
self.onmessage = async function(event) {
  const { id, type, payload } = event.data;
  const startTime = performance.now();
  
  try {
    let result;
    
    switch (type) {
      case 'PROCESS_DATA':
        const processor = new ChunkProcessor(payload.data, payload.chunkSize);
        result = await processor.processChunk(payload.processor);
        break;
        
      case 'IMAGE_GRAYSCALE':
        result = ImageProcessor.grayscale(payload);
        break;
        
      case 'IMAGE_BLUR':
        result = ImageProcessor.blur(payload.imageData, payload.radius);
        break;
        
      case 'SORT_QUICK':
        result = await SortingWorker.quickSort(payload);
        break;
        
      case 'SORT_MERGE':
        result = SortingWorker.mergeSort(payload);
        break;
        
      case 'MATRIX_MULTIPLY':
        result = matrixMultiply(payload.a, payload.b);
        break;
        
      default:
        throw new Error(`未知操作类型: ${type}`);
    }
    
    const duration = performance.now() - startTime;
    
    self.postMessage({
      id,
      type: 'RESULT',
      payload: result,
      duration,
      status: 'success'
    });
    
  } catch (error) {
    self.postMessage({
      id,
      type: 'ERROR',
      error: error.message,
      status: 'failed'
    });
  }
};

// 矩阵乘法(并行优化)
function matrixMultiply(a, b) {
  const rows = a.length;
  const cols = b[0].length;
  const result = new Array(rows);
  
  for (let i = 0; i < rows; i++) {
    result[i] = new Array(cols);
    for (let j = 0; j < cols; j++) {
      let sum = 0;
      for (let k = 0; k < a[0].length; k++) {
        sum += a[i][k] * b[k][j];
      }
      result[i][j] = sum;
    }
  }
  
  return result;
}

🔄 Worker Pool 管理

// ============ Worker 池 ============
class WorkerPool {
  constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
    this.workerScript = workerScript;
    this.poolSize = poolSize;
    this.workers = [];
    this.taskQueue = [];
    this.activeWorkers = new Set();
    this.taskCallbacks = new Map();
    this.nextTaskId = 0;
    
    this.init();
  }
  
  init() {
    for (let i = 0; i < this.poolSize; i++) {
      const worker = new Worker(this.workerScript);
      
      worker.onmessage = (event) => {
        const { id, type, payload, duration, error } = event.data;
        const callback = this.taskCallbacks.get(id);
        
        if (callback) {
          if (type === 'RESULT') {
            callback.resolve({ payload, duration });
          } else if (type === 'ERROR') {
            callback.reject(new Error(error));
          }
          this.taskCallbacks.delete(id);
        }
        
        this.activeWorkers.delete(worker);
        this.processNextTask(worker);
      };
      
      worker.onerror = (error) => {
        console.error('Worker 错误:', error);
        this.activeWorkers.delete(worker);
        this.recycleWorker(worker);
      };
      
      this.workers.push(worker);
    }
  }
  
  processNextTask(worker) {
    if (this.taskQueue.length === 0) return;
    
    const { id, task, resolve, reject } = this.taskQueue.shift();
    this.activeWorkers.add(worker);
    this.taskCallbacks.set(id, { resolve, reject });
    
    worker.postMessage({ id, ...task });
  }
  
  execute(task) {
    return new Promise((resolve, reject) => {
      const id = this.nextTaskId++;
      const availableWorker = this.workers.find(w => !this.activeWorkers.has(w));
      
      if (availableWorker) {
        this.activeWorkers.add(availableWorker);
        this.taskCallbacks.set(id, { resolve, reject });
        availableWorker.postMessage({ id, ...task });
      } else {
        this.taskQueue.push({ id, task, resolve, reject });
      }
    });
  }
  
  recycleWorker(worker) {
    const index = this.workers.indexOf(worker);
    if (index !== -1) {
      const newWorker = new Worker(this.workerScript);
      this.workers[index] = newWorker;
      worker.terminate();
    }
  }
  
  async map(array, processor) {
    const chunkSize = Math.ceil(array.length / this.poolSize);
    const chunks = [];
    
    for (let i = 0; i < array.length; i += chunkSize) {
      chunks.push(array.slice(i, i + chunkSize));
    }
    
    const promises = chunks.map(chunk => 
      this.execute({
        type: 'PROCESS_DATA',
        payload: { data: chunk, processor: processor.toString() }
      })
    );
    
    const results = await Promise.all(promises);
    return results.flat();
  }
  
  terminate() {
    this.workers.forEach(worker => worker.terminate());
    this.workers = [];
    this.taskQueue = [];
    this.activeWorkers.clear();
    this.taskCallbacks.clear();
  }
}

// 使用 Worker Pool
const pool = new WorkerPool('data-processor.worker.js', 8);

// 并行处理大数据
const largeArray = Array.from({ length: 1000000 }, (_, i) => Math.random());

const results = await pool.map(largeArray, (chunk) => {
  return chunk.map(x => Math.sqrt(x) * Math.sin(x));
});

console.log('处理完成:', results.length);

📡 Shared Worker 多页面通信

// ============ Shared Worker ============
// shared-worker.js
const connections = new Set();
let sharedData = {
  counter: 0,
  user: null,
  theme: 'light'
};

self.onconnect = function(event) {
  const port = event.ports[0];
  connections.add(port);
  
  port.onmessage = function(event) {
    const { type, payload, clientId } = event.data;
    
    switch (type) {
      case 'GET_STATE':
        port.postMessage({ type: 'STATE', payload: sharedData });
        break;
        
      case 'UPDATE_STATE':
        sharedData = { ...sharedData, ...payload };
        // 广播给所有连接的页面
        broadcast({ type: 'STATE_CHANGED', payload: sharedData });
        break;
        
      case 'SUBSCRIBE':
        // 订阅事件
        break;
        
      case 'DISCONNECT':
        connections.delete(port);
        break;
    }
  };
  
  port.start();
};

function broadcast(message) {
  connections.forEach(port => {
    port.postMessage(message);
  });
}

// ============ main.js (多个页面共享) ============
const sharedWorker = new SharedWorker('shared-worker.js');

sharedWorker.port.start();

sharedWorker.port.onmessage = (event) => {
  const { type, payload } = event.data;
  
  switch (type) {
    case 'STATE_CHANGED':
      console.log('状态已更新:', payload);
      updateUI(payload);
      break;
      
    case 'STATE':
      console.log('初始状态:', payload);
      break;
  }
};

// 更新共享状态
function updateSharedState(newState) {
  sharedWorker.port.postMessage({
    type: 'UPDATE_STATE',
    payload: newState
  });
}

// 获取当前状态
sharedWorker.port.postMessage({ type: 'GET_STATE' });

🎯 今日挑战

实现一个实时数据处理仪表板,要求:

  1. 使用 Web Worker 处理实时数据流(每秒1000+数据点)
  2. 实现 Worker Pool 管理多个计算任务
  3. 支持数据过滤、聚合、统计运算
  4. 使用 Transferable Objects 优化数据传输
  5. 提供数据处理进度显示
  6. 支持导出处理结果
// 使用示例
const dashboard = new RealtimeDashboard({
  workerPool: 4,
  dataSource: WebSocketStream,
  processors: [
    { type: 'filter', condition: (v) => v > threshold },
    { type: 'aggregate', window: '1s', method: 'average' },
    { type: 'anomaly', algorithm: '3-sigma' }
  ]
});

dashboard.on('data', (processed) => {
  chart.update(processed);
});

dashboard.start();

明日预告:WebSocket 高级应用 - 实时协作、游戏同步与大规模消息推送

💡 多线程箴言:"Worker 不是银弹,但它是突破 JavaScript 单线程限制的关键武器"——合理分配任务,让 UI 永远流畅!