web woker 笔记

Last Updated:

2022-08-12

Web Worker 使用教程

用完即毁

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

几个注意点

Web Worker 有以下几个使用注意点。 (1)同源限制 分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。 (2)DOM 限制 Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以navigator对象和location对象。 (3)通信联系 Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。 (4)脚本限制 Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。 (5)文件限制 Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

tbc1: 等待自己研究 问:同源,是同属于一个一级域名的二级域名的worker脚本文件能否被加载(譬如,主脚本在test.baidu.com上的文件,能通过worker加载test2.baidu.com上的脚本吗? 问:同源,有没有httphttps的分别(例如,主脚本在https://www.baidu.com上的文件,worker能加载http://www.baidu.com上的脚本吗?亦或者相反,主脚本在http上能通过worker加载https的脚本吗?) 问:主线程上的脚本文件,是cdn后的oss上的脚本文件(但前端项目是部署在https://www.petersonlian.com/testworkder上),譬如https://oss.test.com/1.js,那么worker能加载https://oss.test.com/2.js吗?

worker脚本只能访问navigatorlocation对象

worker脚本,无法访问主线程所在网页的DOM对象(譬如document,window,parent等)

worker不能执行alert()confirm(),但可以发ajax

可以使用 XMLHttpRequest 对象发出 AJAX 请求。

worker所加载脚本必须来自网络

Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

基本用法

主进程

let worker = new Worker('worker.js') // worker.js是一个执行在worker里头的脚本

worker.onmessage = function(event) {
    console.log(`接收到来自worker线程的数据: ` + event.data)
    dosomething
}

worker.postMessage() // 给worker线程传送数据,可以是各种类型,包括二进制
worker.terminate() // 关闭worker线程

Worker线程

主线程通讯(接收讯息)

需要监听message事件

this.addEventListener('message', function(e) { console.log(`e.data: ${e.data} `)}, false)

this.onmessage = function (){} // 监听接收来自主进程的通讯;和addEventListner一样

主线程通讯(发送讯息)

this.postMessage() // 用来给主线程发送消息

this.close(), 线程自我关闭

数据通信

主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。

主线程与 Worker 之间也可以交换二进制数据,比如 File、Blob、ArrayBuffer 等类型,也可以在线程之间发送。下面是一个例子。


// 主线程
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);

// Worker 线程
self.onmessage = function (e) {
  var uInt8Array = e.data;
  postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
  postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};

但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。

为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了 写法

// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);

// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);