浏览器之内置四大多线程API

一、为什么 Web Worker 能实现 "多线程"?

浏览器的 JS 引擎(如 V8)本身是单线程的,但浏览器是多进程 / 多线程架构。Web Worker 的本质是 浏览器为 JS 提供的 "额外线程池" ,核心原理:

  1. 线程隔离:Worker 线程与主线程是完全隔离的内存空间(不共享堆内存),通过 "消息队列" 通信(数据传输基于结构化克隆算法,深拷贝)。
  2. 阻塞无关性:Worker 线程的阻塞(如死循环)不会影响主线程,但会导致自身无法响应消息。
  3. 资源限制:浏览器对 Worker 数量有上限(通常同源下不超过 20 个),且单个 Worker 占用的内存有限制(避免滥用系统资源)。

关键区别 :与 Node.js 的 child_process 不同,Web Worker 无法共享内存(需通过 SharedArrayBuffer 实现有限共享,见下文),且受浏览器安全策略(如跨域限制)约束。

总结: webwork是通过js的方式唤起浏览器的内置api使用,辅助前端计算的一种方式,就像fetch、ajaix那样唤起浏览器的接口查询一样。

多线程四大API

Web Worker 是前端 "多线程" 的基础规范,但浏览器针对不同场景设计了 4 种独立的 Worker 类型------ 它们共享 "线程隔离" 的核心思想(避免阻塞主线程),但定位、能力、使用场景完全不同,均为 W3C 标准定义的原生 API(无需第三方库),底层由浏览器独立实现(无依赖关系)。

浏览器 4 种核心 Worker 类型对比表

Worker 类型 核心定位 底层依赖 典型场景
Web Worker 主线程的 "计算助手",处理耗时计算 浏览器线程池 大数据排序、加密解密、复杂算法计算
Shared Worker 多页面共享的 "后台协调者",跨页面通信 浏览器共享线程 多标签页登录状态同步、跨页数据协同
Service Worker 页面离线的 "代理服务器",拦截请求 + 缓存 浏览器后台线程 离线应用、请求拦截优化、浏览器推送通知
Worklet 渲染管线的 "实时处理器",介入渲染流程 浏览器渲染线程 CSS 物理动画、音频降噪、Canvas 渲染优化

二、分类

1. Web Worker(计算型:解决主线程阻塞)

核心能力

  • 独立于主线程的 "计算线程",仅与创建它的主线程通信(一对一);
  • 生命周期与页面绑定(页面关闭则 Worker 销毁);
  • 不阻塞 UI,专门处理耗时计算(避免页面卡顿)。

示例:10 万条数据排序

javascript 复制代码
// 主线程(main.js):发起计算请求,接收结果
if (window.Worker) {
  // 1. 创建 Worker 实例
  const sortWorker = new Worker('sort-worker.js');
  
  // 2. 生成 10 万条随机大数据(模拟复杂计算场景)
  const bigData = Array.from({ length: 100000 }, () => Math.random() * 100000);
  
  // 3. 发送数据给 Worker,触发计算
  sortWorker.postMessage(bigData);
  console.log('主线程:已发送数据,等待排序结果...');
  
  // 4. 接收 Worker 返回的计算结果
  sortWorker.onmessage = (e) => {
    console.log('主线程:排序完成,前 10 条数据:', e.data.slice(0, 10));
    sortWorker.terminate(); // 计算完成,销毁 Worker(避免内存泄漏)
  };
  
  // 5. 监听 Worker 错误(如代码报错)
  sortWorker.onerror = (err) => {
    console.error(`Worker 错误:${err.message}(行号:${err.lineno})`);
    sortWorker.terminate();
  };
} else {
  console.error('当前浏览器不支持 Web Worker');
}
// Worker 线程(sort-worker.js):执行耗时计算
self.onmessage = (e) => {
  const data = e.data;
  console.log('Worker 线程:开始排序 10 万条数据...');
  
  // 耗时计算(示例用内置排序,实际可替换为快排、归并等复杂算法)
  const sortedData = data.sort((a, b) => a - b);
  
  // 向主线程返回结果
  self.postMessage(sortedData);
  self.close(); // Worker 主动关闭,释放资源
};

运行效果

主线程可正常处理用户交互(点击、滚动),排序在后台线程执行,页面无卡顿;计算完成后自动销毁 Worker,无内存泄漏。

2. Shared Worker(协同型:多页面数据同步)

核心能力

  • 同源多页面可共享同一个 Worker 实例(突破 "单页面私有" 限制);
  • 通过 port(通信端口)实现多页面与 Worker 的双向通信;
  • 适合跨页面状态同步(无需重复请求接口)。

示例:多标签页登录状态同步

javascript 复制代码
// 主线程 - 页面 A(login.html):登录后同步状态
if (window.SharedWorker) {
  // 1. 创建 Shared Worker 实例
  const sharedWorker = new SharedWorker('sync-worker.js');
  // 2. 激活通信端口(必须调用 start())
  sharedWorker.port.start();
  
  // 3. 模拟登录按钮点击,发送登录状态给 Worker
  document.getElementById('login-btn').addEventListener('click', () => {
    const userInfo = { username: 'admin', isLogin: true };
    sharedWorker.port.postMessage({ type: 'LOGIN', data: userInfo });
    console.log('页面 A:已发送登录状态');
  });
  
  // 4. 接收 Worker 广播的消息(如其他页面同步的状态)
  sharedWorker.port.onmessage = (e) => {
    console.log('页面 A 收到消息:', e.data);
  };
}
// 主线程 - 页面 B(index.html):实时获取登录状态
if (window.SharedWorker) {
  const sharedWorker = new SharedWorker('sync-worker.js');
  sharedWorker.port.start();
  
  // 1. 向 Worker 请求当前登录状态
  sharedWorker.port.postMessage({ type: 'QUERY_STATUS' });
  
  // 2. 接收状态(页面 A 登录后,页面 B 实时更新)
  sharedWorker.port.onmessage = (e) => {
    if (e.data.type === 'LOGIN_STATUS') {
      document.getElementById('status').textContent = e.data.isLogin 
        ? `已登录:${e.data.username}` 
        : '未登录';
    }
  };
}
// Shared Worker 线程(sync-worker.js):维护全局状态,广播消息
const connections = []; // 存储所有连接的页面端口
let globalState = { isLogin: false, username: '' }; // 全局共享状态
// 监听页面连接(新页面打开时触发)
self.onconnect = (e) => {
  const port = e.ports[0];
  connections.push(port); // 记录新连接的页面
  port.start();
  
  // 处理页面发送的消息
  port.onmessage = (msg) => {
    switch (msg.data.type) {
      case 'LOGIN':
        // 更新全局状态
        globalState = msg.data.data;
        // 广播给所有连接的页面(同步状态到页面 A、B...)
        connections.forEach(p => p.postMessage({ 
          type: 'LOGIN_STATUS', 
          ...globalState 
        }));
        break;
      case 'QUERY_STATUS':
        // 单独响应某个页面的状态查询
        port.postMessage({ type: 'LOGIN_STATUS', ...globalState });
        break;
    }
  };
  
  // 页面关闭时,移除端口(避免内存泄漏)
  port.onclose = () => {
    const index = connections.indexOf(port);
    if (index !== -1) connections.splice(index, 1);
  };
};

运行效果

页面 A 点击 "登录" 后,页面 B 无需刷新,实时显示 "已登录:admin";多标签页共享同一登录状态,无需重复调用登录接口。

3. Service Worker(网络型:离线缓存 + 请求拦截)

核心能力

  • 完全独立于页面,运行在浏览器后台(页面关闭后仍可活动);
  • 拦截所有网络请求,可自定义缓存策略(实现离线访问);
  • 生命周期包含 "安装→激活→运行",需手动管理缓存版本。

示例:离线缓存静态资源 + API 请求

javascript 复制代码
// 主线程(main.js):注册 Service Worker,触发离线逻辑
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      // 1. 注册 Service Worker(脚本需在根目录,确保作用域覆盖所有页面)
      const registration = await navigator.serviceWorker.register('/sw.js');
      console.log('Service Worker 注册成功,作用域:', registration.scope);
      
      // 2. 测试离线请求(首次联网缓存,后续断网可访问)
      fetch('/api/data').then(res => res.json()).then(data => {
        console.log('API 请求结果:', data);
      });
    } catch (err) {
      console.error('Service Worker 注册失败:', err);
    }
  });
}
// Service Worker 线程(sw.js):缓存+请求拦截逻辑
const CACHE_NAME = 'offline-cache-v1'; // 缓存版本(更新时修改版本号)
// 需要缓存的资源列表(静态资源+API接口)
const CACHE_ASSETS = [
  '/', 
  '/index.html',
  '/styles.css',
  '/api/data' // 需缓存的 API 接口
];
// 1. 安装阶段:缓存静态资源(仅首次注册/版本更新时触发)
self.addEventListener('install', (event) => {
  // 等待缓存完成后,再进入激活阶段
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(CACHE_ASSETS)) // 批量缓存资源
      .then(() => self.skipWaiting()) // 跳过等待,立即激活新 Worker(替换旧版本)
  );
});
// 2. 激活阶段:清理旧缓存(避免缓存膨胀)
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      // 删除非当前版本的缓存
      return Promise.all(
        cacheNames.filter(name => name !== CACHE_NAME).map(name => caches.delete(name))
      );
    }).then(() => self.clients.claim()) // 强制所有打开的页面使用新 Worker
  );
});
// 3. 拦截请求:优先从缓存返回,无缓存则请求网络
self.addEventListener('fetch', (event) => {
  // 仅拦截同源的 GET 请求(避免跨域资源和非幂等请求)
  if (event.request.method === 'GET' && event.request.mode === 'same-origin') {
    event.respondWith(
      caches.match(event.request)
        .then(cachedResponse => {
          // 缓存命中:直接返回缓存资源
          if (cachedResponse) {
            console.log('从缓存返回:', event.request.url);
            return cachedResponse;
          }
          // 缓存未命中:发起网络请求,并缓存新结果
          return fetch(event.request).then(networkResponse => {
            caches.open(CACHE_NAME).then(cache => {
              cache.put(event.request, networkResponse.clone()); // 缓存新请求
            });
            return networkResponse;
          });
        })
    );
  }
});

运行效果

  • 首次联网时,自动缓存 index.html、styles.css 和 /api/data;
  • 断网后刷新页面,仍能正常加载页面和 API 数据(从缓存读取);
  • 缓存版本更新时,自动清理旧缓存,避免资源冗余。

4. Worklet(实时型:介入渲染流程)

核心能力

  • 嵌入浏览器渲染管线(如 CSS 引擎线程、音频线程),低延迟(<1ms);
  • 直接干预渲染过程(动画、绘制、音频处理),弥补 Web Worker 通信延迟的缺陷;
  • 细分类型:CSSWorklet(动画)、AudioWorklet(音频)、PaintWorklet(绘制)。

示例:CSSWorklet 实现物理弹性动画

typescript 复制代码
// 主线程(main.js):注册 Worklet,关联动画
if ('CSSWorklet' in window) {
  try {
    // 注册自定义 Worklet(加载动画逻辑脚本)
    await CSSWorklet.addModule('bounce-worklet.js');
    console.log('CSSWorklet 注册成功');
  } catch (err) {
    console.error('CSSWorklet 注册失败:', err);
  }
}
// HTML 结构:动画元素
/*
<div class="box">弹性动画方块</div>
*/
// CSS 样式:绑定 Worklet 动画
/*
.box {
  width: 100px;
  height: 100px;
  background: red;
  /* 使用 Worklet 定义的动画(名称与 Worklet 中注册一致) */
  animation: bounce 2s infinite;
}
/* 定义动画进度(from→to 对应 Worklet 中的 0→1) */
@keyframes bounce {
  from { transform: translateY(0); }
  to { transform: translateY(300px); }
}
*/
// CSSWorklet 线程(bounce-worklet.js):自定义动画逻辑
class BounceWorklet {
  // 每一帧的计算(嵌入渲染管线,实时执行)
  process(inputs, outputs, parameters) {
    const [t] = inputs; // 动画进度(0~1,from→to)
    const [output] = outputs; // 输出结果(最终的 CSS 样式值)
    
    // 物理弹性公式(模拟重力+反弹效果,非匀速动画)
    const bounce = Math.sin(t * Math.PI) * Math.exp(-t * 0.5);
    // 输出 transform 样式(控制方块位置)
    output.value = `translateY(${300 * (1 - bounce)}px)`;
  }
}
// 注册 Worklet 动画(名称需与 CSS 中的 @keyframes 名称一致)
registerAnimator('bounce', BounceWorklet);

运行效果

红色方块以 "物理弹性轨迹" 上下运动(类似小球落地反弹),动画流畅无卡顿;Worklet 运行在渲染线程,延迟极低,避免 Web Worker 通信导致的动画掉帧。

三、混合使用:多 Worker 协同解决复杂场景

场景需求:离线数据分析 Dashboard

需要同时满足 4 个核心需求:

  1. 离线访问(断网后仍可查看历史数据);
  1. 多标签页同步分析结果(无需重复解析);
  1. 后台解析 10 万条 CSV 数据(不阻塞页面);
  1. 实时渲染流畅图表(动画无掉帧)。

完整实现代码(整合 4 种 Worker)

javascript 复制代码
// 主线程(dashboard.js):整合所有 Worker,协调流程
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      // 1. 第一步:注册 Service Worker(离线缓存)
      const swRegistration = await navigator.serviceWorker.register('/sw.js');
      console.log('Service Worker 注册成功');
      // 2. 第二步:创建 Shared Worker(多页面同步)
      const sharedWorker = new SharedWorker('sync-worker.js');
      sharedWorker.port.start();
      // 3. 第三步:创建 Web Worker(CSV 数据解析)
      const csvWorker = new Worker('csv-worker.js');
      // 4. 第四步:注册 CSSWorklet(图表动画优化)
      if ('CSSWorklet' in window) {
        await CSSWorklet.addModule('chart-worklet.js');
        console.log('CSSWorklet 注册成功');
      }
      // 5. 页面元素:文件上传、图表容器、状态文本
      const fileInput = document.getElementById('file-upload');
      const chartContainer = document.getElementById('chart-container');
      const statusText = document.getElementById('status');
      // 6. 监听文件上传:触发 CSV 解析
      fileInput.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (file && file.name.endsWith('.csv')) {
          statusText.textContent = '正在解析 CSV 文件...';
          csvWorker.postMessage(file); // 发送文件给 Web Worker
        } else {
          statusText.textContent = '请上传 CSV 格式文件!';
        }
      });
      // 7. 接收 Web Worker 解析结果:渲染+同步+缓存
      csvWorker.onmessage = (e) => {
        const analysisResult = e.data; // 解析结果(数据数组+统计信息)
        
        // 7.1 渲染图表(用 CSSWorklet 优化动画)
        renderChart(analysisResult, chartContainer);
        
        // 7.2 同步结果到其他标签页(通过 Shared Worker)
        sharedWorker.port.postMessage({
          type: 'ANALYSIS_RESULT',
          data: analysisResult
        });
        
        // 7.3 缓存结果到 Service Worker(支持离线访问)
        if (navigator.serviceWorker.controller) {
          navigator.serviceWorker.controller.postMessage({
            type: 'CACHE_RESULT',
            key: 'last-csv-analysis',
            data: analysisResult
          });
        }
        
        // 7.4 更新状态文本
        statusText.textContent = `解析完成:共 ${analysisResult.totalCount} 条</doubaocanvas>

四、关于self

arduino 复制代码
// 1. 来源:Worker 线程的内置全局对象,浏览器自动注入,无需定义
// 2. 作用:等同于主线程的 window,是 Worker 线程访问自身能力的入口
// 3. 隔离性:不同 Worker 的 self 是独立实例,互不干扰(如 Web Worker 的 self ≠ Service Worker 的 self)
// 4. 核心能力:接收/发送消息(onmessage/postMessage)、管理生命周期(close)、调用 Worker 专属 API
Worker 中 self 的简单代码示例
javascript 复制代码
// Worker 线程(calc-worker.js)
// self 指向当前 Web Worker 实例,仅用于计算相关逻辑
self.onmessage = (e) => {
  const { num1, num2 } = e.data;
  const sum = num1 + num2; // 简单计算:两数相加
  self.postMessage(sum); // 用 self 向主线程返回结果
  self.close(); // 用 self 主动关闭 Worker,释放资源
};
// 主线程(main.js)- 配合使用
const calcWorker = new Worker('calc-worker.js');
calcWorker.postMessage({ num1: 10, num2: 20 }); // 发数据给 Worker
calcWorker.onmessage = (e) => {
  console.log('计算结果:', e.data); // 输出 30
};
注意:
lua 复制代码
// 注意 1:Worker 中不能用 window,必须用 self(因 Worker 无窗口概念)
// 注意 2:self 无法访问 DOM(如 document、body),仅能处理非 UI 逻辑
// 注意 3:简单场景可省略 self(如 onmessage = () => {} 等同于 self.onmessage = () => {}),但复杂场景建议保留,增强可读性

五、生产环境使用的平衡点

1. 错误处理与健壮性

  • Worker 内部错误不会冒泡到主线程,需单独监听:

    javascript 复制代码
    // 主线程
    worker.onerror = (err) => {
      console.error(`Worker错误:${err.message}`);
      worker.terminate(); // 出错后销毁Worker,避免内存泄漏
    };
    
    // Worker线程
    self.onerror = (err) => {
      self.postMessage({ type: 'ERROR', message: err.message });
      self.close(); // 主动关闭
    };
  • 网络错误:Worker 脚本加载失败(404)时,主线程会触发 error 事件,需捕获并降级处理。

2. 内存管理与资源回收

  • 避免创建过多 Worker:每个 Worker 都是独立线程,占用内存和 CPU,建议通过 "Worker 池" 复用(如用 p-queue 管理 Worker 实例)。
  • 及时销毁无用 Worker:worker.terminate()(主线程主动销毁,立即终止)或 self.close()(Worker 主动关闭,清理后终止)。
  • 警惕内存泄漏:Worker 中若持有 setInterval、未关闭的 fetch 请求等,即使调用 terminate 也可能导致内存泄漏,需先清理资源。

3. 跨域与安全策略

  • Worker 脚本必须与主线程同源(协议、域名、端口一致),若需加载跨域脚本,需通过 importScripts 加载且服务器允许跨域(Access-Control-Allow-Origin)。
  • 禁止访问 file:// 协议下的脚本(浏览器安全限制),本地开发需用 http-server 启动服务。
  • 敏感操作限制:Worker 中无法使用 localStorage(部分浏览器支持,但规范不推荐),建议用 IndexedDB 存储大量数据(异步 API,不阻塞)。

4. 兼容性处理与降级方案

  • 浏览器支持:IE 完全不支持,Edge 12+、Chrome 4+、Firefox 3.5+ 支持基本特性,SharedArrayBufferSharedWorker 兼容性较差。

  • 降级逻辑:

    scss 复制代码
    if (window.Worker) {
      // 使用Worker
    } else {
      // 降级到主线程执行(给用户提示"当前浏览器可能卡顿")
      heavyTask();
    }

六、性能陷阱

  1. 过度使用 Worker 导致性能反降 小数据计算(如几毫秒可完成的操作)用 Worker 反而会增加通信开销(序列化 + 消息传递),建议仅对 执行时间 > 50ms 的任务使用 Worker。

  2. 频繁通信导致主线程阻塞 若 Worker 与主线程高频次 postMessage(如每秒 hundreds 次),序列化数据会占用主线程资源,导致卡顿。解决方案:

    • 批量发送数据(累计一定量后再通信);
    • SharedArrayBuffer 减少序列化开销。
  3. Worker 中滥用同步 API Worker 虽然不阻塞 UI,但内部的同步操作(如 XMLHttpRequest 的同步请求、大量同步循环)会阻塞自身线程,导致无法响应消息。建议优先使用异步 API(fetchsetImmediate)。

  4. 忽略线程优先级 浏览器会给 Worker 分配较低的线程优先级,若需 "近实时" 处理(如游戏帧计算),可能出现延迟。此时可考虑 requestIdleCallback 结合 Worker,利用主线程空闲时间处理。

相关推荐
熬夜敲代码的小N11 分钟前
Vue (Official)重磅更新!Vue Language Tools 3.2功能一览!
前端·javascript·vue.js
90后的晨仔16 分钟前
用 Python 脚本一键重命名序列帧图片的名称
前端
辰同学ovo16 分钟前
Vue 2 路由指南:从入门到实战优化
前端·vue.js
小彭努力中17 分钟前
1.在 Vue 3 中使用 Cesium 快速展示三维地球
前端·javascript·vue.js·#地图开发·#cesium·#vue3
一棵开花的树,枝芽无限靠近你20 分钟前
【face-api.js】1️⃣基于Tensorflow.js的人脸识别项目开源项目
javascript·开源·tensorflow·face-api.js
一字白首26 分钟前
Vue3 进阶,新特性 defineOptions/defineModel+Pinia 状态管理全解析
前端·javascript·vue.js
90后的晨仔31 分钟前
🛠️ 为什么配置 ~/.ssh/config 后,Sourcetree 就能正常推送了?
前端
Sylus_sui40 分钟前
Vue2 与 Vue3 数据双向绑定:区别与原理详解
前端·javascript·vue.js
ConardLi1 小时前
AI:我裂开了!现在的大模型评测究竟有多变态?
前端·人工智能·后端
这是你的玩具车吗1 小时前
能和爸妈讲明白的大模型原理
前端·人工智能·机器学习