🚀 主线程卡死用户要骂娘?Web Worker让你的应用丝滑如德芙

🎯 学习目标:掌握Web Worker的核心原理和实战应用,解决主线程阻塞问题,让应用性能丝滑提升

📊 难度等级 :中级

🏷️ 技术标签#Web Worker #多线程 #性能优化 #异步处理

⏱️ 阅读时间:约8分钟


🌟 引言

在日常的前端开发中,你是否遇到过这样的困扰:

  • 大数据处理时页面卡死:处理几万条数据时,页面直接卡成PPT,用户点击毫无反应
  • 复杂计算阻塞UI:图片处理、数据分析等计算密集型任务让整个应用假死
  • 文件上传/下载卡顿:大文件操作时,其他功能完全无法使用
  • 实时数据处理性能差:WebSocket接收大量数据时,页面渲染严重滞后

今天分享6个Web Worker的核心技巧,让你的应用告别卡顿,用户体验丝滑如德芙!


💡 核心技巧详解

1. 基础Worker创建:解放主线程的第一步

🔍 应用场景

当你需要执行耗时的JavaScript计算,但又不想阻塞用户界面时

❌ 常见问题

直接在主线程中执行大量计算,导致页面卡死

javascript 复制代码
// ❌ 主线程阻塞写法
const processLargeData = (data) => {
  const result = [];
  // 模拟大量计算
  for (let i = 0; i < 1000000; i++) {
    result.push(data[i] * Math.random());
  }
  return result;
};

// 页面会卡死几秒钟
const result = processLargeData(largeArray);

✅ 推荐方案

使用Web Worker将计算任务移到后台线程

javascript 复制代码
/**
 * 创建Web Worker处理大数据
 * @description 将耗时计算移到Worker线程,避免阻塞主线程
 * @param {Array} data - 需要处理的数据
 * @returns {Promise} 处理结果
 */
const processDataWithWorker = (data) => {
  return new Promise((resolve, reject) => {
    // ✅ 创建Worker
    const worker = new Worker('/workers/data-processor.js');
    
    // 发送数据到Worker
    worker.postMessage({ type: 'PROCESS_DATA', data });
    
    // 监听Worker返回结果
    worker.onmessage = (event) => {
      const { type, result, error } = event.data;
      
      if (type === 'PROCESS_COMPLETE') {
        resolve(result);
        worker.terminate(); // 清理Worker
      } else if (type === 'PROCESS_ERROR') {
        reject(new Error(error));
        worker.terminate();
      }
    };
    
    // 错误处理
    worker.onerror = (error) => {
      reject(error);
      worker.terminate();
    };
  });
};

💡 核心要点

  • Worker文件独立:Worker代码必须在单独的JS文件中
  • 通信机制:主线程和Worker通过postMessage/onmessage通信
  • 资源清理:使用完毕后调用terminate()立即终止Worker线程

🎯 实际应用

Worker文件示例(/workers/data-processor.js):

javascript 复制代码
// Worker线程代码
self.onmessage = (event) => {
  const { type, data } = event.data;
  
  if (type === 'PROCESS_DATA') {
    try {
      const result = [];
      // 在Worker线程中执行计算
      for (let i = 0; i < data.length; i++) {
        result.push(data[i] * Math.random());
      }
      
      // 返回结果
      self.postMessage({
        type: 'PROCESS_COMPLETE',
        result
      });
    } catch (error) {
      self.postMessage({
        type: 'PROCESS_ERROR',
        error: error.message
      });
    }
  }
};

2. 进度反馈机制:让用户知道进度

🔍 应用场景

处理大量数据时,需要实时显示处理进度,提升用户体验

❌ 常见问题

长时间计算没有进度反馈,用户不知道是否卡死

javascript 复制代码
// ❌ 没有进度反馈
const processData = async (data) => {
  // 用户只能干等,不知道进度
  return await heavyComputation(data);
};

✅ 推荐方案

在Worker中实现进度反馈机制

javascript 复制代码
/**
 * 带进度反馈的数据处理
 * @description 在处理过程中实时反馈进度
 * @param {Array} data - 数据数组
 * @param {Function} onProgress - 进度回调函数
 * @returns {Promise} 处理结果
 */
const processWithProgress = (data, onProgress) => {
  return new Promise((resolve, reject) => {
    const worker = new Worker('/workers/progress-processor.js');
    
    worker.postMessage({ 
      type: 'START_PROCESS', 
      data,
      batchSize: 1000 // 每批处理1000条
    });
    
    worker.onmessage = (event) => {
      const { type, progress, result, error } = event.data;
      
      switch (type) {
        case 'PROGRESS_UPDATE':
          // ✅ 实时更新进度
          onProgress(progress);
          break;
          
        case 'PROCESS_COMPLETE':
          resolve(result);
          worker.terminate();
          break;
          
        case 'PROCESS_ERROR':
          reject(new Error(error));
          worker.terminate();
          break;
      }
    };
  });
};

💡 核心要点

  • 分批处理:将大任务分成小批次,便于进度反馈
  • 进度计算:准确计算完成百分比
  • 用户体验:及时更新UI进度条

🎯 实际应用

进度处理Worker(/workers/progress-processor.js):

javascript 复制代码
// 带进度的Worker处理
self.onmessage = (event) => {
  const { type, data, batchSize } = event.data;
  
  if (type === 'START_PROCESS') {
    const total = data.length;
    const result = [];
    let processed = 0;
    
    // 分批处理
    const processBatch = () => {
      const end = Math.min(processed + batchSize, total);
      
      for (let i = processed; i < end; i++) {
        result.push(data[i] * Math.random());
      }
      
      processed = end;
      const progress = Math.round((processed / total) * 100);
      
      // 发送进度更新
      self.postMessage({
        type: 'PROGRESS_UPDATE',
        progress
      });
      
      if (processed < total) {
        // 使用setTimeout避免阻塞
        setTimeout(processBatch, 0);
      } else {
        // 处理完成
        self.postMessage({
          type: 'PROCESS_COMPLETE',
          result
        });
      }
    };
    
    processBatch();
  }
};

3. 文件处理优化:大文件上传不再卡顿

🔍 应用场景

处理大文件上传、图片压缩、文件解析等IO密集型任务

❌ 常见问题

在主线程中处理文件,导致页面无响应

javascript 复制代码
// ❌ 主线程处理大文件
const handleFileUpload = (file) => {
  const reader = new FileReader();
  reader.onload = (e) => {
    // 主线程被阻塞
    const processedData = processLargeFile(e.target.result);
    uploadFile(processedData);
  };
  reader.readAsArrayBuffer(file);
};

✅ 推荐方案

使用Worker处理文件,支持分片上传

javascript 复制代码
/**
 * Worker文件处理器
 * @description 在Worker中处理文件,避免阻塞主线程
 * @param {File} file - 文件对象
 * @param {Object} options - 处理选项
 * @returns {Promise} 处理结果
 */
const processFileInWorker = (file, options = {}) => {
  return new Promise((resolve, reject) => {
    const worker = new Worker('/workers/file-processor.js');
    
    // 将文件转为ArrayBuffer发送给Worker
    const reader = new FileReader();
    reader.onload = (e) => {
      worker.postMessage({
        type: 'PROCESS_FILE',
        fileData: e.target.result,
        fileName: file.name,
        fileType: file.type,
        options
      });
    };
    
    worker.onmessage = (event) => {
      const { type, result, error, progress } = event.data;
      
      switch (type) {
        case 'FILE_PROCESSED':
          resolve(result);
          worker.terminate();
          break;
          
        case 'PROCESSING_PROGRESS':
          // 文件处理进度
          options.onProgress?.(progress);
          break;
          
        case 'PROCESSING_ERROR':
          reject(new Error(error));
          worker.terminate();
          break;
      }
    };
    
    reader.readAsArrayBuffer(file);
  });
};

💡 核心要点

  • ArrayBuffer传输:使用ArrayBuffer在主线程和Worker间传输文件数据
  • 分片处理:大文件分片处理,避免内存溢出
  • 进度监控:实时反馈文件处理进度

🎯 实际应用

文件处理Worker示例:

javascript 复制代码
// /workers/file-processor.js
self.onmessage = (event) => {
  const { type, fileData, fileName, options } = event.data;
  
  if (type === 'PROCESS_FILE') {
    try {
      const chunkSize = 1024 * 1024; // 1MB分片
      const chunks = [];
      let offset = 0;
      
      while (offset < fileData.byteLength) {
        const chunk = fileData.slice(offset, offset + chunkSize);
        chunks.push(chunk);
        offset += chunkSize;
        
        // 发送进度
        const progress = Math.round((offset / fileData.byteLength) * 100);
        self.postMessage({
          type: 'PROCESSING_PROGRESS',
          progress
        });
      }
      
      // 返回处理结果
      self.postMessage({
        type: 'FILE_PROCESSED',
        result: {
          chunks,
          fileName,
          totalSize: fileData.byteLength
        }
      });
    } catch (error) {
      self.postMessage({
        type: 'PROCESSING_ERROR',
        error: error.message
      });
    }
  }
};

4. SharedArrayBuffer:高性能数据共享

🔍 应用场景

需要在主线程和Worker间高频率共享大量数据时

❌ 常见问题

频繁的postMessage传输大数据,性能开销巨大

javascript 复制代码
// ❌ 频繁传输大数据
const updateLargeDataset = (data) => {
  // 每次都要复制整个数据集
  worker.postMessage({ type: 'UPDATE', data: largeArray });
};

✅ 推荐方案

使用SharedArrayBuffer实现零拷贝数据共享

javascript 复制代码
/**
 * 创建共享内存数据处理器
 * @description 使用SharedArrayBuffer实现高性能数据共享
 * @param {number} size - 共享内存大小
 * @returns {Object} 共享数据处理器
 */
const createSharedDataProcessor = (size) => {
  //  检查SharedArrayBuffer支持和安全上下文
  if (typeof SharedArrayBuffer === 'undefined') {
    throw new Error('SharedArrayBuffer is not supported in this environment');
  }
  
  // 检查是否在安全的跨域隔离环境中
  if (!crossOriginIsolated) {
    console.warn('SharedArrayBuffer requires cross-origin isolation. Consider using regular postMessage.');
    throw new Error('SharedArrayBuffer requires cross-origin isolation');
  }
  
  //  创建共享内存
  const sharedBuffer = new SharedArrayBuffer(size * 4); // 4字节per int32
  const sharedArray = new Int32Array(sharedBuffer);
  
  const worker = new Worker('/workers/shared-processor.js');
  
  // 将共享内存传递给Worker
  worker.postMessage({
    type: 'INIT_SHARED_MEMORY',
    sharedBuffer
  });
  
  return {
    sharedArray,
    worker,
    
    /**
     * 更新共享数据
     * @param {number} index - 索引
     * @param {number} value - 值
     */
    updateData: (index, value) => {
      //  直接修改共享内存,无需传输
      Atomics.store(sharedArray, index, value);
      
      // 通知Worker数据已更新
      worker.postMessage({
        type: 'DATA_UPDATED',
        index
      });
    },
    
    /**
     * 批量处理数据
     * @param {Function} callback - 处理完成回调
     */
    processBatch: (callback) => {
      worker.postMessage({ type: 'PROCESS_BATCH' });
      
      worker.onmessage = (event) => {
        if (event.data.type === 'BATCH_COMPLETE') {
          callback(event.data.result);
        }
      };
    }
  };
};

💡 核心要点

  • 安全上下文要求:SharedArrayBuffer需要HTTPS和跨域隔离环境
  • 原子操作:使用Atomics API确保线程安全
  • 内存共享:避免数据复制,提升性能
  • 同步机制:合理使用通知机制协调数据访问

🎯 实际应用

共享内存Worker示例:

javascript 复制代码
// /workers/shared-processor.js
let sharedArray;

self.onmessage = (event) => {
  const { type, sharedBuffer, index } = event.data;
  
  switch (type) {
    case 'INIT_SHARED_MEMORY':
      sharedArray = new Int32Array(sharedBuffer);
      break;
      
    case 'DATA_UPDATED':
      // 读取共享内存中的数据
      const value = Atomics.load(sharedArray, index);
      console.log(`Data at ${index} updated to ${value}`);
      break;
      
    case 'PROCESS_BATCH':
      let sum = 0;
      for (let i = 0; i < sharedArray.length; i++) {
        sum += Atomics.load(sharedArray, i);
      }
      
      self.postMessage({
        type: 'BATCH_COMPLETE',
        result: { sum, average: sum / sharedArray.length }
      });
      break;
  }
};

5. Worker池管理:复用Worker提升性能

🔍 应用场景

需要频繁创建Worker处理任务,但创建开销较大时

❌ 常见问题

每次任务都创建新Worker,资源浪费严重

javascript 复制代码
// ❌ 频繁创建Worker
const processTasks = async (tasks) => {
  for (const task of tasks) {
    // 每个任务都创建新Worker
    const worker = new Worker('/workers/task-processor.js');
    await processTask(worker, task);
    worker.terminate();
  }
};

✅ 推荐方案

实现Worker池,复用Worker实例

javascript 复制代码
/**
 * Worker池管理器
 * @description 管理Worker实例池,提升性能和资源利用率
 */
class WorkerPool {
  constructor(workerScript, poolSize = 4) {
    this.workerScript = workerScript;
    this.poolSize = poolSize;
    this.workers = [];
    this.busyWorkers = new Set();
    this.taskQueue = [];
    
    this.initWorkers();
  }
  
  /**
   * 初始化Worker池
   */
  initWorkers = () => {
    for (let i = 0; i < this.poolSize; i++) {
      const worker = new Worker(this.workerScript);
      worker.id = i;
      this.workers.push(worker);
    }
  };
  
  /**
   * 获取可用Worker
   * @returns {Worker|null} 可用的Worker实例
   */
  getAvailableWorker = () => {
    return this.workers.find(worker => !this.busyWorkers.has(worker));
  };
  
  /**
   * 执行任务
   * @param {Object} taskData - 任务数据
   * @returns {Promise} 任务结果
   */
  executeTask = (taskData) => {
    return new Promise((resolve, reject) => {
      const task = { taskData, resolve, reject };
      
      const availableWorker = this.getAvailableWorker();
      if (availableWorker) {
        this.runTask(availableWorker, task);
      } else {
        //  没有可用Worker时加入队列
        this.taskQueue.push(task);
      }
    });
  };
  
  /**
   * 运行任务
   * @param {Worker} worker - Worker实例
   * @param {Object} task - 任务对象
   */
  runTask = (worker, task) => {
    this.busyWorkers.add(worker);
    
    const handleMessage = (event) => {
      const { type, result, error } = event.data;
      
      if (type === 'TASK_COMPLETE') {
        task.resolve(result);
        this.releaseWorker(worker);
      } else if (type === 'TASK_ERROR') {
        task.reject(new Error(error));
        this.releaseWorker(worker);
      }
      
      worker.removeEventListener('message', handleMessage);
    };
    
    worker.addEventListener('message', handleMessage);
    worker.postMessage({
      type: 'EXECUTE_TASK',
      data: task.taskData
    });
  };
  
  /**
   * 释放Worker
   * @param {Worker} worker - 要释放的Worker
   */
  releaseWorker = (worker) => {
    this.busyWorkers.delete(worker);
    
    // 处理队列中的下一个任务
    if (this.taskQueue.length > 0) {
      const nextTask = this.taskQueue.shift();
      this.runTask(worker, nextTask);
    }
  };
  
  /**
   * 销毁Worker池
   */
  destroy = () => {
    this.workers.forEach(worker => worker.terminate());
    this.workers = [];
    this.busyWorkers.clear();
    this.taskQueue = [];
  };
}

💡 核心要点

  • 池化管理:预创建Worker实例,避免频繁创建销毁
  • 任务队列:Worker忙碌时将任务加入队列等待
  • 资源复用:最大化Worker利用率

🎯 实际应用

使用Worker池处理批量任务:

javascript 复制代码
// 创建Worker池
const workerPool = new WorkerPool('/workers/batch-processor.js', 4);

/**
 * 批量处理任务
 * @param {Array} tasks - 任务列表
 * @returns {Promise} 所有任务结果
 */
const processBatchTasks = async (tasks) => {
  const promises = tasks.map(task => workerPool.executeTask(task));
  
  try {
    const results = await Promise.all(promises);
    return results;
  } catch (error) {
    console.error('批量任务处理失败:', error);
    throw error;
  }
};

// 使用示例
const tasks = [
  { type: 'calculate', data: [1, 2, 3] },
  { type: 'process', data: 'some text' },
  { type: 'transform', data: { x: 1, y: 2 } }
];

processBatchTasks(tasks)
  .then(results => console.log('所有任务完成:', results))
  .catch(error => console.error('任务失败:', error));

6. 错误处理与调试:Worker开发最佳实践

🔍 应用场景

Worker开发过程中的错误处理、调试和性能监控

❌ 常见问题

Worker错误难以调试,缺乏有效的错误处理机制

javascript 复制代码
// ❌ 简陋的错误处理
const worker = new Worker('/workers/processor.js');
worker.onerror = (error) => {
  console.log('Worker出错了'); // 信息不够详细
};

✅ 推荐方案

完善的错误处理和调试机制

javascript 复制代码
/**
 * 增强型Worker管理器
 * @description 提供完善的错误处理、调试和监控功能
 */
class EnhancedWorker {
  constructor(scriptPath, options = {}) {
    this.scriptPath = scriptPath;
    this.options = options;
    this.worker = null;
    this.messageHandlers = new Map();
    this.errorHandlers = [];
    this.performanceMetrics = {
      tasksCompleted: 0,
      totalExecutionTime: 0,
      errors: 0
    };
    
    this.initWorker();
  }
  
  /**
   * 初始化Worker
   */
  initWorker = () => {
    try {
      this.worker = new Worker(this.scriptPath);
      this.setupEventHandlers();
    } catch (error) {
      this.handleError('WORKER_CREATION_FAILED', error);
    }
  };
  
  /**
   * 设置事件处理器
   */
  setupEventHandlers = () => {
    //  详细的消息处理
    this.worker.onmessage = (event) => {
      const { type, taskId, result, error, timestamp } = event.data;
      
      if (error) {
        this.handleError('WORKER_TASK_ERROR', error, { taskId });
        return;
      }
      
      // 性能监控
      if (timestamp) {
        const executionTime = Date.now() - timestamp;
        this.updatePerformanceMetrics(executionTime);
      }
      
      // 调用对应的处理器
      const handler = this.messageHandlers.get(taskId);
      if (handler) {
        handler.resolve(result);
        this.messageHandlers.delete(taskId);
      }
    };
    
    //  增强的错误处理
    this.worker.onerror = (errorEvent) => {
      this.handleError('WORKER_RUNTIME_ERROR', {
        message: errorEvent.message,
        filename: errorEvent.filename,
        lineno: errorEvent.lineno,
        colno: errorEvent.colno
      });
    };
    
    //  未捕获异常处理(消息序列化错误)
    this.worker.onmessageerror = (errorEvent) => {
      this.handleError('WORKER_MESSAGE_ERROR', {
        message: 'Failed to deserialize message from worker',
        data: errorEvent.data
      });
    };
  };
  
  /**
   * 执行任务
   * @param {string} type - 任务类型
   * @param {*} data - 任务数据
   * @param {Object} options - 执行选项
   * @returns {Promise} 任务结果
   */
  executeTask = (type, data, options = {}) => {
    return new Promise((resolve, reject) => {
      const taskId = this.generateTaskId();
      const timestamp = Date.now();
      
      // 注册处理器
      this.messageHandlers.set(taskId, { resolve, reject });
      
      // 设置超时
      if (options.timeout) {
        setTimeout(() => {
          if (this.messageHandlers.has(taskId)) {
            this.messageHandlers.delete(taskId);
            reject(new Error(`Task ${taskId} timeout after ${options.timeout}ms`));
          }
        }, options.timeout);
      }
      
      // 发送任务
      this.worker.postMessage({
        type,
        taskId,
        data,
        timestamp,
        options
      });
    });
  };
  
  /**
   * 错误处理
   * @param {string} errorType - 错误类型
   * @param {Error} error - 错误对象
   * @param {Object} context - 错误上下文
   */
  handleError = (errorType, error, context = {}) => {
    this.performanceMetrics.errors++;
    
    const errorInfo = {
      type: errorType,
      message: error.message || error,
      stack: error.stack,
      timestamp: new Date().toISOString(),
      context,
      workerScript: this.scriptPath
    };
    
    //  详细的错误日志
    console.error('Worker Error:', errorInfo);
    
    // 调用错误处理器
    this.errorHandlers.forEach(handler => {
      try {
        handler(errorInfo);
      } catch (handlerError) {
        console.error('Error handler failed:', handlerError);
      }
    });
  };
  
  /**
   * 添加错误处理器
   * @param {Function} handler - 错误处理函数
   */
  onError = (handler) => {
    this.errorHandlers.push(handler);
  };
  
  /**
   * 更新性能指标
   * @param {number} executionTime - 执行时间
   */
  updatePerformanceMetrics = (executionTime) => {
    this.performanceMetrics.tasksCompleted++;
    this.performanceMetrics.totalExecutionTime += executionTime;
  };
  
  /**
   * 获取性能报告
   * @returns {Object} 性能指标
   */
  getPerformanceReport = () => {
    const { tasksCompleted, totalExecutionTime, errors } = this.performanceMetrics;
    
    return {
      tasksCompleted,
      averageExecutionTime: tasksCompleted > 0 ? totalExecutionTime / tasksCompleted : 0,
      totalExecutionTime,
      errors,
      errorRate: tasksCompleted > 0 ? (errors / tasksCompleted) * 100 : 0
    };
  };
  
  /**
   * 生成任务ID
   * @returns {string} 唯一任务ID
   */
  generateTaskId = () => {
    return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  };
  
  /**
   * 销毁Worker
   */
  destroy = () => {
    if (this.worker) {
      this.worker.terminate();
      this.worker = null;
    }
    this.messageHandlers.clear();
    this.errorHandlers = [];
  };
}

💡 核心要点

  • 全面错误处理:捕获各种类型的Worker错误
  • 性能监控:跟踪任务执行时间和成功率
  • 调试支持:提供详细的错误信息和上下文

🎯 实际应用

使用增强型Worker处理任务:

javascript 复制代码
// 创建增强型Worker
const enhancedWorker = new EnhancedWorker('/workers/enhanced-processor.js');

// 添加错误处理器
enhancedWorker.onError((errorInfo) => {
  // 发送错误到监控系统
  sendToMonitoring(errorInfo);
});

// 执行任务
const processData = async () => {
  try {
    const result = await enhancedWorker.executeTask(
      'COMPLEX_CALCULATION',
      { numbers: [1, 2, 3, 4, 5] },
      { timeout: 5000 }
    );
    
    console.log('任务完成:', result);
    
    // 查看性能报告
    const report = enhancedWorker.getPerformanceReport();
    console.log('性能报告:', report);
    
  } catch (error) {
    console.error('任务执行失败:', error);
  }
};

📊 技巧对比总结

技巧 使用场景 优势 注意事项
基础Worker 简单计算任务 解放主线程,提升响应性 需要独立JS文件,通信开销
进度反馈 长时间处理任务 提升用户体验,实时反馈 增加通信频率,需要合理控制
文件处理 大文件操作 避免UI阻塞,支持分片 内存管理,错误处理复杂
SharedArrayBuffer 高频数据共享 零拷贝,高性能 浏览器兼容性,线程安全
Worker池 批量任务处理 资源复用,提升效率 内存占用,池大小调优
错误处理 生产环境应用 稳定性高,便于调试 代码复杂度增加

🎯 实战应用建议

最佳实践

  1. 合理选择Worker类型:根据任务特点选择Dedicated Worker或Shared Worker
  2. 优化通信频率:减少不必要的postMessage调用,批量传输数据
  3. 内存管理:及时清理Worker资源,避免内存泄漏
  4. 错误边界:建立完善的错误处理机制,提升应用稳定性
  5. 性能监控:跟踪Worker性能指标,持续优化

性能考虑

  • Worker创建开销:Worker创建有一定开销,适合长时间运行的任务
  • 数据传输成本:大数据传输考虑使用SharedArrayBuffer或Transferable Objects
  • 浏览器兼容性:SharedArrayBuffer需要特定的安全上下文
  • 调试复杂性:Worker调试相对复杂,需要完善的日志机制

💡 总结

这6个Web Worker技巧在日常开发中能够显著提升应用性能,掌握它们能让你的应用:

  1. 基础Worker创建:彻底解决主线程阻塞问题
  2. 进度反馈机制:提供优秀的用户体验
  3. 文件处理优化:高效处理大文件操作
  4. SharedArrayBuffer:实现高性能数据共享
  5. Worker池管理:最大化资源利用率
  6. 错误处理与调试:保证生产环境稳定性

希望这些技巧能帮助你在前端开发中告别卡顿,写出更丝滑的应用!


🔗 相关资源


💡 今日收获:掌握了6个Web Worker核心技巧,这些知识点在实际开发中非常实用。

如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有任何问题也欢迎在评论区讨论。 🚀

相关推荐
南雨北斗3 小时前
vue3 attribute绑定
前端
小桥风满袖4 小时前
极简三分钟ES6 - Promise
前端·javascript
breeze_whisper4 小时前
当前端收到一个比梦想还大的数字:BigInt处理指南
前端·面试
小喷友4 小时前
阶段四:实战(项目开发能力)
前端·rust
小高0074 小时前
性能优化零成本:只加3行代码,FCP从1.8s砍到1.2s
前端·javascript·面试
用户66982061129824 小时前
vue3 hooks、utils、data这几个文件夹分别是放什么的?
javascript·vue.js
子兮曰4 小时前
🌏浏览器硬件API大全:30个颠覆性技术让你重新认识Web开发
前端·javascript·浏览器
即兴小索奇4 小时前
Google AI Mode 颠覆传统搜索方式,它是有很大可能的
前端·后端·架构
大虾写代码4 小时前
nvm和nrm的详细安装配置,从卸载nodejs到安装NVM管理nodejs版本,以及安装nrm管理npm版本
前端·npm·node.js·nvm·nrm