参考
链接:https://juejin.cn/post/7139718200177983524
来源:稀土掘金
介绍
Web Worker
是 HTML5 标准的一部分,这一规范定义了一套 API,允许我们在 js 主线程之外开辟新的 Worker 线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力。
因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。
虽然 Worker 线程是在浏览器环境中被唤起,但是它与当前页面窗口运行在不同的全局上下文中,我们常用的顶层对象 window
,以及 parent
对象在 Worker 线程上下文中是不可用的。另外,在 Worker 线程上下文中,操作 DOM 的行为也是不可行的,document
对象也不存在。但是,location
和navigator
对象可以以可读方式访问。除此之外,绝大多数 Window 对象上的方法和属性,都被共享到 Worker 上下文全局对象 WorkerGlobalScope 中。同样,Worker 线程上下文也存在一个顶级对象 self
使用
创建woker
const worker = new Worker(path,options);
/*
example const worker = new Worker("worker.js")
*/
主线程和worker 线程数据传递
主线程和worker线程都通过postMessage方法来发送消息,监听message事件来接收消息
示例:
script.js
const worker = new Worker("worker.js")
worker.addEventListener('message',(e)=>{
console.log(e.data)
})
//向worker发送消息
worker.postMessage("hello,i am main thread")
------------
worker.js
self.addEventListener('message',(e)=>{
//接收到来自主线程的消息后,向主线程发送消息
console.log(e.data)
self.postMessage("hello,i am worker")
})
---
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="script.js"></script>
</body>
</html>
postMessage()
方法可以接收的参数可以是字符串,对象,数组等
主线程与worker线程之间的数据传递是传值而不是传地址,
所以当你发送一个对象的时候,到了另一方就已经不是同一个对象了
传送数据支持的类型
向worker线程传送大文件 IO密集型操作
Transferable Objects 与大文件传输:深入探讨
Transferable Objects 是一种非常高效的机制,可以将大块二进制数据(例如 ArrayBuffer)从一个上下文(如主线程)转移到另一个上下文(如 Worker 线程),而无需复制数据。这对于处理大文件非常有益,因为它可以显著提高性能并减少内存占用。
为什么 Transferable Objects 适合大文件传输?
避免数据复制: 当你将一个 ArrayBuffer 标记为 Transferable 并传递给 Worker 线程时,这个 ArrayBuffer 的所有权实际上被转移了。主线程不再拥有对这个数据的访问权,而 Worker 线程获得了对它的独占访问权。这避免了在两个线程之间复制一份数据,节省了内存。
提高性能: 由于避免了数据复制,Transferable Objects 可以显著提高大文件传输的性能。尤其是对于 I/O 密集型操作,这种性能提升更为明显。
Transferable Objects 的局限性
所有权转移: 虽然 Transferable Objects 的主要优势在于所有权转移,但这也意味着主线程在传递数据后将无法再访问该数据。如果主线程还需要保留一份副本,则需要在传递之前进行复制。
数据类型限制: Transferable Objects 主要用于传输 ArrayBuffer。对于其他类型的数据,需要先将其转换为 ArrayBuffer,然后才能使用 Transferable Objects。
浏览器兼容性: 虽然大多数现代浏览器都支持 Transferable Objects,但在一些较老的浏览器中可能存在兼容性问题。
你的问题:拥有权是否转移?
是的,Transferable Objects 的核心特性就是所有权转移。 当你将一个 ArrayBuffer 标记为 Transferable 并传递给 Worker 线程时,这个 ArrayBuffer 的所有权就从主线程转移到了 Worker 线程。主线程将无法再访问这个数据。
在指使用 postMessage
方法,将一个 ArrayBuffer
对象作为参数传递给 Worker 线程,并且在 postMessage
的第二个参数中再次传递这个 ArrayBuffer
,以实现数据所有权的转移。
示例代码:
// 主线程
const worker = new Worker('worker.js');
const arrayBuffer = new ArrayBuffer(1024 * 1024); // 创建一个 1MB 的 ArrayBuffer
worker.postMessage(arrayBuffer, [arrayBuffer]); // 将 ArrayBuffer 标记为 Transferable 并发送
// Worker 线程
self.onmessage = (event) => {
const receivedBuffer = event.data;
// 在 Worker 线程中处理 receivedBuffer
};
监听错误消息
监听方式和接收消息是一致的
关闭worker线程
// 主线程
const worker = new Worker("worker.js");
worker.terminate();
// worker线程
self.close()
无论在主线程中关闭worker,还是在worker线程内部关闭worker,worker线程中当前event loop中的任务还会继续执行,而worker线程中下一个event loop中的任务,会被直接忽略,不会继续执行
区别是,在主线程手动关闭 worker,主线程与 worker 线程之间的连接都会被立刻停止,即使 worker 线程当前的 Event Loop 中仍有待执行的任务继续调用 postMessage()
方法,但主线程不会再接收到消息。
在 worker 线程内部关闭 worker,不会直接断开与主线程的连接,而是等 worker 线程当前的 Event Loop 所有任务执行完,再关闭。也就是说,在当前 Event Loop 中继续调用 postMessage()
方法,主线程还是能通过监听message
事件收到消息的。
我们来验证一下:
我们让主线程接收到一条来自worker的消息后就直接terminate,在worker线程中,我们使用先用postMessage发送一条消息,然后setInterval每隔一秒发送一个消息
script.js
const worker = new Worker("worker.js");
worker.addEventListener('message',(e)=> {
console.log(e.data);
worker.terminate();
})
worker.postMessage("Hello from main thread");
------
worker.js
self.addEventListener('message', () => {
postMessage("hello from worker")
setInterval(() => {
const message = `Message from worker at ${new Date().toISOString()}`;
postMessage(message);
}, 1000);
})
我们来浏览器看看什么效果
能看到主线程接收到第一条消息以后就不再接收来自worker线程的消息了
那如果在worker线程中关闭呢?
self.close()
是在Worker线程内部调用的,用于主动终止当前Worker的执行。
当调用 self.close()
后,Worker会立即停止执行后续的代码,包括当前Worker中的所有计时器(如 setInterval
或 setTimeout
)、回调、事件监听等。任何正在进行的操作都会中断。
script.js
// 创建Worker
const worker = new Worker('worker.js');
// 监听消息
worker.onmessage = (event) => {
console.log("Message from Worker:", event.data);
};
// 监听错误
worker.onerror = (event) => {
console.error("Error from Worker:", event.message);
console.error("File:", event.filename);
console.error("Line:", event.lineno, "Column:", event.colno);
};
// 发送启动消息
worker.postMessage("start");
---
worker.js
self.addEventListener('message', () => {
postMessage("hello from worker");
setTimeout(() => {
postMessage("Final message before closing.");
self.close(); // 延迟关闭
}, 5000); // 确保有时间发送消息
setInterval(() => {
const message = `Message from worker at ${new Date().toISOString()}`;
postMessage(message);
}, 1000);
});
worker线程引用其它js文件
importScripts('filepath')
我们来写一个例子
util.js,其中包括一个计算函数,我们用主线程向worker线程中发送一个对象,其中包括a,b,然后让worker线程拿到这个对象,取出其中的值,然后计算出来发送给主线程
utils.js
const add = (a,b)=>{
return a + b
}
script.js
// 创建Worker
const worker = new Worker('worker.js');
// 监听消息
worker.onmessage = (event) => {
console.log("Message from Worker:", event.data);
};
// 监听错误
worker.onerror = (event) => {
console.error("Error from Worker:", event.message);
console.error("File:", event.filename);
console.error("Line:", event.lineno, "Column:", event.colno);
};
// 发送启动消息
worker.postMessage({
a: 1,
b: 2
});
worker.js
importScripts('util.js')
self.addEventListener('message', (e) => {
let res = add(e.data.a, e.data.b)
postMessage("calculate result is " + res)
});
ESMODULE模式
script.js
// 创建Worker
const worker = new Worker('worker.js',{
type: 'module'
});
// 监听消息
worker.onmessage = (event) => {
console.log("Message from Worker:", event.data);
};
// 监听错误
worker.onerror = (event) => {
console.error("Error from Worker:", event.message);
console.error("File:", event.filename);
console.error("Line:", event.lineno, "Column:", event.colno);
};
// 发送启动消息
worker.postMessage({
a: 1,
b: 2
});
----
worker.js
import {add} from './util.js'
self.addEventListener('message', (e) => {
let res = add(e.data.a, e.data.b)
let res2 = add(e.data.a,e.data.b)
postMessage("calculate a + b is " + res)
postMessage("calculate a - b is " + res2)
});
export default self
---
util.js
const add = (a,b)=>{
return a + b;
}
const minus = (a,b)=>{
return a - b;
}
export {add,minus}
主线程和worker线程之间哪些类型不能传递?
postMessage()
传递的数据可以是由结构化克隆算法处理的任何值或 JavaScript 对象,包括循环引用。
结构化克隆算法不能处理的数据:
-
Error
以及Function
对象; -
DOM 节点
-
对象的某些特定参数不会被保留
RegExp
对象的lastIndex
字段不会被保留- 属性描述符,setters 以及 getters(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write
- 原形链上的属性也不会被追踪以及复制。