1 前言
js 最初是设计到浏览器中运行的,为了防止多个线程操作 DOM,js 执行器被设计为单线程,但是碰到大量计算的场景时( 图像处理,视频解码等),js 线程会造成长时间阻塞,影响用户体验,为解决这一弊端,Web Worker 应运而生
2 关于 Web Wroker
Web Worker
是 HTML5 标准的一部分,这一规范定义了一系列 API,允许我们在 js 主线程之外开信的 worker 线程,并将 js 脚本运行其中,它赋予了开发者操作多线程的能力。当有大量运算任务时,可以把运算任务交给 WebWorker 去处理,再把结果返回主线程,这样 js 只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了
3 Web Worker 能干什么
虽然 Worker 线程是在浏览器环境中被唤起,但是它与当前页面窗口运行在不同的全局上下文中,我们常用的顶层对象 window,以及 parent 对象在 Worker 线程上下文中是不可用的。另外,在 Worker 线程上下文中,操作 DOM 的行为也是不可行的,document 对象也不存在。但是,location 和 navigator 对象可以以可读方式访问。除此之外,绝大多数 Window 对象上的方法和属性,都被共享到 Worker 上下文全局对象 WorkerGlobalScope 中。同样,Worker 线程上下文也存在一个顶级对象 self。
4 Web Worker 使用
4.1 创建 Web Worker
const worker = new Worker(path, options);
参数说明:
-
path:有效的 js 脚本的地址,必须遵守同源策略。无效的 js 地址或者违反同源策略,会抛出 SECURITY_ERR 类型错误
-
options.type 可选:用以指定 worker 类型。该值可以是 classic 或 module。 如未指定,将使用默认值 classic
-
options.credentials 可选:用以指定 worker 凭证。该值可以是 omit, same-origin,或 include。如果未指定,
或者 type 是 classic,将使用默认值 omit (不要求凭证)
-
options.name:在 DedicatedWorkerGlobalScope 的情况下,用来表示 worker 的 scope 的一个 DOMString 值,主要用于调试目
4.2 主线程与 worker 线程数据传递
主线程与 worker 通过 postMesage
方法进行数据传递,以及message
事件来接收消息
# main.js
const myWorker = new Worker("../js/worker.js")
const obj = {
name: "xmk"
}
myWorker.postMessage(obj)
myWorker.addEventListener('message', e => {
console.log("mainjs")
// false 说明是值传递 而不是传地址
console.log(obj === e.data)
})
或:
worker.onmessage = function(e) {
console.log('Received message from worker: ' + e.data);
};
# worker.js
self.addEventListener('message', e => {
console.log('workerjs')
console.log(typeof e.data)
self.postMessage('this is workerjs')
})
或:
onmessage = function(e) {
console.log('Received message from main script: ' + e.data);
// 处理数据并返回
var result = 'Processed data: ' + event.data;
postMessage(result);
};
# 控制台输出
workerjs
object
mainjs
false
4.3 监听错误信息
# mainjs
const myWorker = new Worker('../js/worker.js'); // 创建worker
myWorker.addEventListener('error', err => {
console.log(err.message);
});
myWorker.addEventListener('messageerror', err => {
console.log(err.message)
});
# workerjs
self.addEventListener('error', err => {
console.log(err.message);
});
self.addEventListener('messageerror', err => {
console.log(err.message);
});
4.4 关闭 Worker 线程
worker 线程的关闭在主线程和 worker 线程都能进行操作,但对 worker 线程的影响略有不同。
相同点: worker 线程当前的 Event Loop 中的任务会继续执行。至于 worker 线程下一个 Event Loop 中的任务,则会被直接忽略,不会继续执行。
不同点:在主线程手动关闭 worker,主线程与 worker 线程之间的连接都会被立刻停止,即使 worker 线程当前的 Event Loop 中仍有待执行的任务继续调用 postMessage() 方法,但主线程不会再接收到消息。
在 worker 线程内部关闭 worker,不会直接断开与主线程的连接,而是等 worker 线程当前的 Event Loop 所有任务执行完,再关闭。也就是说,在当前 Event Loop 中继续调用 postMessage() 方法,主线程还是能通过监听 message 事件收到消息的。
-
mainjs
myWorker.terminate(); // 关闭 worker
-
workerjs
self.close(); // 直接执行 close 方法
4.4.1 主线程关闭关闭 worker
# mainjs
const myWorker = new Worker("../js/worker.js")
myWorker.postMessage("main send")
myWorker.addEventListener('message', e => {
console.log(e.data)
myWorker.terminate();
})
# workerjs
self.addEventListener('message', e => {
console.log(e.data)
postMessage('worker return')
// 开启一个宏任务
setTimeout(() => {
console.log('timre start')
postMessage('timer end')
})
// 开启一个微任务
Promise.resolve().then(() => {
console.log('Promise start')
postMessage('Promise end')
})
for (let i = 0; i < 100; i++) {
console.log('for start')
if ((i = 100)) {
postMessage('for end ')
}
}
})
# console
main send
for start
worker return
Promise start
timre start
- 主线程只会接收到 worker 线程第一次通过 postMessage() 发送的消息,后面的消息不会接收到;
- worker 线程当前 Event Loop 里的任务会继续执行,包括微任务
- worker 线程里 setTimeout 创建的下一个 Event Loop 任务队列没有执行
4.4.2 worker 内部关闭
# workerjs
self.addEventListener('message', e => {
console.log(e.data)
postMessage('worker return')
self.close()
... ...
# console
main send
for start
Promise start
worker return
for end
Promise end
- worker 线程当前的 Event Loop 任务队列中的 postMessage() 事件都会被主线程监听到
4.5 Worker 线程引入其他 js 对象
使用importScript()
方法
# utils.js
const add = (a,b) => { a + b }
# worker.js
importScript('./utils.js')
console.log(add(3,4))
4.6 Wroker 的 ESMoudle 模式
# main.js
const worker = new worker('./js/worker.js',{
type : 'module'
})
# utils.js
export default add = (a,b)=>a + b
# worker.js
import add from './utils.js'
add(1,2)
4.7 Worker 和主线程可以传递的数据
postMessage() 传递的数据可以是由结构化克隆算法处理的任何值或 JavaScript 对象,包括循环引用。
结构化克隆算法不能
处理的数据:
- Error 以及 Function 对象
- DOM 节点
- 对象的某些特定参数不会被保留
- RegExp 对象的 lastIndex 字段不会被保留
- 属性描述符,setters 以及 getters(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write
- 原形链上的属性也不会被追踪以及复制。
结构化算法支持的数据:
- 所有的原始数据类型(Symbol 除外)
- Boolean 对象
- String 对象
- Date
- RegExp ( lastIndex 字段不会被保留。)
- Blob[Blob - Web API 接口参考 | MDN]
- File
- FileList
- ArrayBuffer
- ArrayBufferView
- ImageData
- Array
- Object
- Map
- Set
5 SharedWorker
SharedWorker 是一种特殊类型的 Worker,可以被多个浏览上下文访问,比如多个 windows,iframes 和 workers,但这些浏览上下文必须同源。它们实现于一个不同于普通 worker 的接口,具有不同的全局作用域:SharedWorkerGlobalScope ,但是继承自 WorkerGlobalScope
6 使用
# main.js
const worker = new Worker('../js/calc.js')
function calc() {
const num = parseInt(document.getElementById('num').value)
worker.postMessage(num)
}
worker.onmessage = function (e) {
document.getElementById('result').innerHTML = e.data
}
# calc.js
function calc(num) {
let result = 0
let startTime = performance.now()
// 计算求和(模拟复杂计算)
for (let i = 0; i <= num; i++) {
result += i
}
// 由于是同步计算,在没计算完成之前下面的代码都无法执行
const time = performance.now() - startTime
console.log('总计算花费时间:', time)
self.postMessage(result)
}
self.onmessage = function (e) {
calc(e.data)
}