Web Worker【JS深入知识汇点11】

253 阅读2分钟

Web Worker 是 H5 标准的一部分,这一规范定义了一套 API,它允许一段 javascript 程序运行在主线程之外的另一个线程中。主线程代码会立即向下继续执行。一旦创建一个 web worker,它就可以把消息发送到创建它的 js 代码,通过将消息发布到该代码指定的事件处理程序。

快速上手

创建 Worker

// 传入要在 worker 线程中运行的脚本的 URI
var myWorker = new Worker('my_task.js') // 必须符合同源策略

//my_task.js 内的代码
var i = 0;
function timedCount() {
	i = i + 1;
    postMessage(i);
    setTimeout(timeCount, 1000)
}
timeCount();

通过 URL.createObjectURL() 创建 URL 对象,可以实现创建内嵌的 Worker

var myTask = '...'//上面 my_task.js 代码
var blob = new Blob([myTask])
var myWorker = new Worker(window.URL.createObjectURL(blob))

Worker 线程数据通讯方式

Worker 与主线程之间的通信是通过 onmessagepostMessage 方法实现的。只能单纯的传递数据,不能传递复杂的引用类型。并且,传递的数据是经过拷贝生成的一个副本,在一端对数据修改,不会影响到另外一端

var myTask = `
	// 当对象接收到 message 事件时被调用,或者可以使用 addEventListener
	onmessage = function(e) {
    	var data = e.data;
        data.push('hello')
        // data 会被结构化克隆算法序列化,发送给别的 worker, 
        // Error 和 Function 不能被结构化克隆算法序列化,会抛出 DATA_CLONE_ERR
        // postMesage 创建一个 MessageEvent 并派发一个带有该反序列化消息的 MessageEvent 事件到接收端口上
        postMessage(data)
    }
`
var blob = new Blob([myTask])
var myWorker = new Worker(window.URL.createObjectURL(blob));
myWorker.onmessage = function(e) {
	var data = e.data;
    console.log('page:', data) // [1, 2, 3, 'hello']
	console.log('arr:', arr) // [1, 2, 3]
}
var arr = [1, 2, 3]
// 给 worker 发送信息
myWorker.postMessage(arr)

通过可转让对象来传递数据

通过可转让对象将数据在主页面和 Worker 之间进行来回穿梭。可转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝操作。这意味着当传递大数据时,会获得极大的性能提升。

var uInt8Array = new Uint8Array(1024*1024*32)
for (var i = 0; i < uInt8Array.length; ++i) {
	uInt8Array[i] = i;
}
console.log(uInt8Array.length) // 33554432
var myTask = `
	onmessage = function(e) {
    	var data = e.data;
        console.log('worker:', data)
    }
`
var blob = new Blob([mytask]);
var myWorker = new Worker(window.URL.createbjectURL(blob));
myWorker.postMessage(uInt8Array.buffer, [uInt*Array.buffer])
// 传递后长度为8
console.log(uInt8Array.length)

Worker 上下文

Worker 上下文的最顶层对象是 WorkerGlobalScope ,无法访问 window、以及与 window 相关的 DOM API,但是可以和 setTimeout 和 setInterbal 等协作

WorkerGlobalScope作用域下的常用属性:

  • self:self属性是 WorkerGlobalScope 对象本身的引用
  • location(只读):该属性表示这个工作线程的脚本资源的绝对 URL
  • close:关闭当前线程
  • importScripts:全局函数 importScripts() 可以引入脚本,该函数接受0个或多个 URI 作为参数,但脚本的下载顺序不固定,这个过程是同步的,直到所有脚本下载并运行完毕,importScripts()才会返回。
  • XMLHttpRequest:发 AJAX 请求
  • setTimeout、setInterval、addEventListener、postMessage

终止

myWorker.terminate() // 主页面终止
self.close() // Worker 线程调用

错误处理

当 worker 出现运行错误时,它的 onerror 事件处理函数会被调用。它会收到一个实现了 ErrorEvent 接口名为 error 的事件。该事件不会冒泡,而且可以被取消。

错误事件有三个实用的属性:

  • filename: 发生错误的脚本文件名
  • lineno: 出现错误的行号
  • messagee:可读性良好的错误消息
myWorker.onerror = function (e) {
	console.log(`Error: line ${e.lineno} in ${e.filename} : ${e.message}`)
}

Shared Worker

Web Worker 规范中定义了两类工作线程,分别是专用线程 Dedicated Worker 和共享线程 Shared Worker ,Dedicated Worker 只能为一个页面所使用,而 Shared Worker 可以被多个页面所共享(只有大约 41.66% 的浏览器支持)

var myWorker = new SharedWorker('my_task.js');
// sharedWorker 实例对象,需要通过 port 属性来访问到主要方法
myWorker.port.start()
myWorker.port.postMessage('Hello, I\'m main')

// my_task.js
concent = function(e) {
	//需要获取一个 post 对象来进行启动和其他操作
	var port = e.ports[0];
    port.addEventListener('message', function(e) {
    	var workResult = 'Result: '
        port.postMesage(workerResult)
    })
    port.start()
}

需要注意的点

  • 有同源限制
  • 无法访问 DOM 节点
  • 运行在另一个上下文中,无法使用 window 对象
  • web worker 不会影响主线程,但如果和主线程频繁交互,主线程由于需要处理交互,仍有可能使页面发生阻塞。
  • 共享线程可以被多个浏览器上下文调用,但所有这些浏览器上下文必须同源

应用场景

  1. 数学运算:用来做后台计算
  2. 图像处理:通过从 中获取数据,把图像分割成几个不同的区域,并且推送给不同的 Workers 来做计算,对图像进行像素级的处理,再把处理后的图像数据返回给主页面
  3. 大数据处理