
2025-04-24 日报 Day166

今日的鸡汤
当你能量越来越高的时候,你开始真正理解每一个人,没有好坏,没有对错,只是他处在不同的能量频率,呈现出了不同的状态,做了不同的选择。言慢者贵,性柔者富,德厚者旺。请允许自己做自己,也允许别人做别人,你会活得更洒脱。
今日学习内容
1、JS 红皮书 P801- 第二十七章:工作者线程
今日笔记
1、委托任务到子工作者线程: 有时候可能需要在工作者线程中再创建子工作者线程。在有多个 CPU 核心的时候,使用多个子工作者线程可以实现并行计算。使用多个子工作者线程前要考虑周全,确保并行计算的投入确实能够得到收益,毕竟同时运行多个子线程会有很大计算成本。
除了路径解析不同,创建子工作者线程与创建普通工作者线程是一样的。子工作者线程的脚本路径根据父工作者线程而不是相对于网页来解析。来看下面的例子(注意额外的 js 目录):
main.js
const worker = new Worker(‘./js/worker.js’);
// worker
// subworker
js/worker.js
console.log(‘worker’);
const worker = new Worker(‘./subworker.js’);
js/subworker.js
console.log(‘subworker’);
注意 顶级工作者线程的脚本和子工作者线程的脚本都必须从与主页相同的源加载。
2、处理工作者线程错误: 如果工作者线程脚本抛出了错误,该工作者线程沙盒可以阻止它打断父线程的执行。如下例所示,其中的 try/catch 块不会捕获到错误:
main.js
try {
const worker = new Worker(‘./worker.js’);
console.log(‘no error’);
} catch(e) {
console.log(‘caught error’);
}
// no error
worker.js
throw Error(‘foo’);
不过,相应的错误事件仍然会冒泡到工作者线程的全局上下文,因此可以通过在 Worker 对象上设置错误事件侦听器访问到。下面看这个例子:
main.js
const worker = new Worker(‘./worker.js’);
worker.onerror = console.log;
// ErrorEvent {message: “Uncaught Error: foo”}
worker.js
throw Error(‘foo’);
3、与专用工作者线程通信: 与工作者线程的通信都是通过异步消息完成的,但这些消息可以有多种形式。
- 使用postMessage(): 最简单也最常用的形式是使用 postMessage()传递序列化的消息。下面来看一个计算阶乘的例子:
factorialWorker.js
function factorial(n) {
let result = 1;
while(n) { result *= n–; }
return result;
}
self.onmessage = ({data}) => {
self.postMessage(${data}! = ${factorial(data)}
);
};
main.js
const factorialWorker = new Worker(‘./factorialWorker.js’);
factorialWorker.onmessage = ({data}) => console.log(data);
factorialWorker.postMessage(5);
factorialWorker.postMessage(7);
factorialWorker.postMessage(10);
// 5! = 120
// 7! = 5040
// 10! = 3628800
对于传递简单的消息,使用 postMessage()在主线程和工作者线程之间传递消息,与在两个窗口间传递消息非常像。主要区别是没有 targetOrigin 的限制,该限制是针对 Window.prototype.postMessage 的,对 WorkerGlobalScope.prototype.postMessage 或 Worker.prototype. postMessage 没有影响。这样约定的原因很简单:工作者线程脚本的源被限制为主页的源,因此没有必要再去过滤了。 - 使用MessageChannel: 无论主线程还是工作者线程,通过 postMessage()进行通信涉及调用全局对象上的方法,并定义一个临时的传输协议。这个过程可以被 Channel Messaging API 取代,基于该 API 可以在两个上下文间明确建立通信渠道。
MessageChannel 实例有两个端口,分别代表两个通信端点。要让父页面和工作线程通过MessageChannel 通信,需要把一个端口传到工作者线程中,如下所示:…to be continued1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34worker.js
// 在监听器中存储全局 messagePort
let messagePort = null;
function factorial(n) {
let result = 1;
while(n) { result *= n--; }
return result;
}
// 在全局对象上添加消息处理程序
self.onmessage = ({ports}) => {
// 只设置一次端口
if (!messagePort) {
// 初始化消息发送端口,
// 给变量赋值并重置监听器
messagePort = ports[0];
self.onmessage = null;
// 在全局对象上设置消息处理程序
messagePort.onmessage = ({data}) => {
// 收到消息后发送数据
messagePort.postMessage(`${data}! = ${factorial(data)}`);
};
}
};
main.js
const channel = new MessageChannel();
const factorialWorker = new Worker('./worker.js');
// 把`MessagePort`对象发送到工作者线程
// 工作者线程负责处理初始化信道
factorialWorker.postMessage(null, [channel.port1]);
// 通过信道实际发送数据
channel.port2.onmessage = ({data}) => console.log(data);
// 工作者线程通过信道响应
channel.port2.postMessage(5);
// 5! = 120