前端知识点:Web Worker

298 阅读4分钟

资料来源于阮一峰老师的博客Web API 接口参考 | MDN

Web Worker是什么?

  • 要理解 Web Worker 首先要知道JavaScript语言是单线程的。
  • 单线程:所有任务都在一个线程上完成,一次只能做一件事,一件事没做完,后面的任务都得排队,好比JavaScript是旅游景点的公共厕所,只有一个坑位,不论男女都得一个一个排着队等里面人出来了下一个才能上厕所。
  • Web Worker:旅游景点一看这不行,我得新建一个厕所,缓解用户的如厕排队时间,这个新建的厕所就是Web Worker

作用

  • Web Worker是为JavaScript创建了一个多线程环境,在主线程创造了工作线程,将一些任务分配给工作线程运行,好处是一些占用性能、时间较高的任务被工作线程负担,主线程就会流畅(主线程一般处理UI交互)。

副作用

  • Web Worker线程一但被创建就会一直运行,不会被主线程的任务所打断,这也导致工作线程是比较耗费资源的,所以一但用完就要立即关闭。

需要注意的地方

  1. Web Worker是同源的,所以分配给它运行的任务、文件也必须和主线程同源
  2. Worker 所在的全局对象和主线程的不一样,这也导致了它无法读取主线程所在网页的DOM对象,无法使用document、window、parent这些对象。但 worker 线程可以使用 navigator对象location对象
  3. Worker 线程和主线程上下文环境不同,导致他们不能直接通信,必须要通过消息完成通信
  4. Worker 线程不能执行 alert() 方法 和 confirm() 方法,但可以使用XMLHttprequest对象发出AJAX请求

基本用法

主线程

  1. 兼容
  • 假如需要在项目中使用Worker,但又要考虑兼容问题那么谨慎起见,把代码写在if里是一个好的选择
if(window.Worker){
    // 代码写这里
}
  1. 构造
  • 主线程通过采用 new 关键字调用 Worker 的构造函数,新建一个Worker线程
  • 注意:Worker() 构造函数的参数是一个脚本文件,由于Worker不能读取本地文件,所以这个脚本必须来自网络,如果没有下载成功,Worker会默默失败。
const worker = new Worker('work.js')
  1. 通信
  • 主线程与worker线程进行通信需要在主线程调用postMessage()方法,向Worker发消息
  • postMessage()方法的参数就是主线程传给Worker的数据。可以是各种数据类型,包括二进制数据
worker.postMessage('你好')
worker.postMessage({method: 'echo', args: ['Work'])
  • 接着主线程需要通过 worker.onmessage() 指定监听函数接收子线程发回来的消息
  • 监听函数参数的data属性可以获取 Worker 发来的数据。
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
  doSomething();
}

function doSomething() {
  // 执行任务
  worker.postMessage('Work done!');
}
  • 当Worker 完成任务后,主线程就可以关掉它,节约性能
worker.terminate()

Worker 线程

  1. 通信
  • Worker线程内需要一个监听函数,监听 message 事件
  • self代表worker线程自身,也就是worker线程的全局对象
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
  doSomething();
}

function doSomething() {
  // 执行任务
  worker.postMessage('Work done!');
}

// 等同于下方代码

// 写法一
this.addEventListener('message', function (e) {
  this.postMessage('You said: ' + e.data);
}, false);

// 写法二
addEventListener('message', function (e) {
  postMessage('You said: ' + e.data);
}, false);
  • 使用self.onmessage指定监听函数。监听函数的参数是一个时间对象,它的data属性包含主线程发来的数据。用self.postMessage() 方法向主线程发送消息。
  • 可以根据主线程发来的不同数据,让worker线程调用不同的方法
self.addEventListener('message', function (e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg);
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);
  1. 关闭线程
  • 在worker线程中,可以调用其自身的close()方法进行关闭
close()
  1. 加载脚本
  • 在worker内部如果需要加载其他脚本,可以使用importScripts()方法
importScripts('script1.js');

// 加载多个脚本

importScripts('script1.js', 'script2.js');

错误处理

  1. 主线程监听Worker错误,如果发生错误,Worker会触发主线程的error事件
worker.onerror(function (event) {
  console.log([
    'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
  ].join(''));
});
  1. Worker内部监听error事件
worker.addEventListener('error', function (event) {
  // ...
});

使用场景

  1. 在很耗费主线程性能的程序里,就可以考虑使用Web Worker,防止页面卡死带来的用户体验问题
  2. 加密:项目中如果有前端数据加密的操作,也能放在Worker线程进行,让其完成工作的纯算法
  3. 预加载:优化网站或web应用改进数据加载时间,利用Web Workers提前加载和存储数据。