Hello,大家好,我是 Sunday
这段时间有不少同学在面试中遇到了 web worker 的问题。所以今天咱们就通过这篇文章,把 web worker 搞明白!
Web Worker 到底是什么?
因为 JS 是单线程(主线程)的,这意味着它一次只能做一件事。
当你在浏览器中执行大量的计算任务(如数据处理、复杂算法等)时,主线程会被阻塞,导致页面无法响应用户交互,影响用户体验。换句话说,当主线程上执行耗时任务时,UI 会被卡死,用户无法进行正常的操作。
例如,假设我们要执行一个耗时的任务,比如对一个非常大的数据集合进行排序或计算。在主线程上执行这类任务时,页面会变得不响应,直到任务完成后,UI 才会刷新。
<div class="box">hello</div>
for (let i = 0; i < 1000000; i++) {
console.log(i)
}
document.querySelector('.box').innerHTML = '程序员Sunday'
而 web worker 就可以解决掉卡顿的问题。
图片
Web Worker 是一种 Web API,它提供了在 后台线程 中运行 JavaScript 的能力。这使得我们可以将耗时的计算任务移至后台线程,避免了主线程被阻塞,从而提升应用性能。
想要使用 Web Worker,那么需要先明确 一个变量、一个构造、两个方法:
变量 self
self 变量是 Web Worker 中的全局上下文对象。它类似于浏览器中的 window 对象,但是在 Web Worker 中,window 是不可用的,因为 Worker 是在一个独立的线程中运行的。
self 代表了 Worker 自己的全局作用域,所有在 Worker 内部定义的全局变量和方法都挂载在 self 上。例如:
self.onmessage = function(event) {
console.log('收到主线程消息:', event.data);
};
构造函数 Worker
创建一个 Web Worker 实例需要使用 Worker 构造函数。该构造函数接受一个字符串参数,指向包含 Worker 代码的 JavaScript 文件的路径。这意味着 Worker 代码和主线程代码是分开的,Worker 可以独立执行:
const worker = new Worker('worker.js');
PS:Web Worker 只能加载与同源策略相同的脚本,因此需要确保 Worker 文件在同一个域下,或者设置适当的 CORS 头部来允许跨域访问。
方法一:onmessage
onmessage 是 Worker 中常用的方法之一。它用于接收来自主线程通过 postMessage 发送的数据。
当主线程调用 worker.postMessage() 发送数据时,Worker 会触发 self.onmessage 事件,并接收数据。示例如下:
self.onmessage = function(event) {
console.log('收到来自主线程的消息:', event.data);
// 处理数据并返回给主线程
self.postMessage('计算完成');
};
方法二 postMessage
postMessage 是 Web Worker 与主线程之间通信的核心方法之一。主线程或 Worker 都可以调用该方法向对方发送数据。它必须接受一个参数,该参数将被传输到对方。在主线程中,postMessage 的调用如下:
worker.postMessage('开始计算');
在 Worker 中,使用 self.postMessage() 来发送结果或数据回主线程:
self.postMessage('结果数据');
以下是一段相对完整的实例代码
// worker.js
self.onmessage = () => {
for (let i = 0; i < 1000000; i++) {
console.log(i)
}
}
// index.html
<script>
const worker = new Worker('./worker.js')
worker.postMessage(1)
document.querySelector('.box').innerHTML = '程序员Sunday'
</script>
Web Worker 的注意事项
对于 Web Worker 来说,我们除了需要知道它的使用方式之外,还有几个关键的注意事项需要掌握
1. 无法操作 DOM
Web Worker 是在独立的线程中运行的,而不是在主线程中。因此,它无法直接操作 DOM。
这一点是 Web Worker 的一个重要限制。由于 Web Worker 与主线程之间的环境是隔离的,无法直接访问或修改网页的 DOM 元素。这意味着,你无法通过 Web Worker 来更新页面内容、修改样式或进行 UI 交互。
不过,虽然 Web Worker 无法直接操作 DOM,但我们仍然可以通过 主线程 和 Web Worker 之间的 消息传递机制 来实现间接的 DOM 操作。
Web Worker 可以通过 postMessage 向主线程发送数据,然后由主线程接收数据并操作 DOM。例如:
// 主线程
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
// 在这里操作 DOM
document.getElementById('result').textContent = event.data;
};
worker.postMessage('开始计算');
// Worker 中的代码
self.onmessage = function(event) {
// 进行一些计算
const result = '计算结果';
postMessage(result); // 结果传递回主线程
};
通过这种方式,Web Worker 可以处理复杂的计算或数据处理任务,然后将结果通过消息传递的方式传递回主线程,由主线程来更新 DOM,保持了线程间的良好分离。
2. 错误处理(Handle Errors)
Web Worker 是在独立的线程中运行的,这意味着任何错误或异常都不会像在主线程中那样直接影响用户界面。因此,对于 Web Worker 的错误处理需要特别注意。
常见的错误类型
Web Worker 中可能遇到的常见错误包括:
- SyntaxError:Worker 文件中的语法错误。
- ReferenceError:引用不存在的变量或函数。
- TypeError:不正确的变量类型使用。
- PostMessage 错误:传递给 postMessage 的对象不符合序列化要求(例如,无法序列化的对象)。
如何捕获 Worker 错误?
Web Worker 提供了 onerror 事件来捕获 Worker 线程中的错误。你可以为 Worker 实例设置错误处理回调函数。例如:
worker.onerror = function(event) {
console.error('Worker 错误:', event.message, '在', event.filename, '的', event.lineno, '行');
};
这个错误处理回调将捕获并打印出 Worker 中的错误信息,包括错误消息、文件名和出错行号。
Worker 内部的错误处理
在 Worker 内部,你还可以使用 try...catch 来捕获异常并将错误信息传递回主线程。例如:
self.onmessage = function(event) {
try {
// 执行可能出错的代码
let result = someFunction(event.data);
postMessage(result);
} catch (error) {
postMessage('错误:' + error.message); // 传递错误信息回主线程
}
};
这样可以确保 Worker 在执行过程中出现异常时不会导致线程崩溃,而是能够优雅地处理错误并将信息反馈给主线程。
使用 terminate 和 close
在某些情况下,如果 Web Worker 发生严重错误或者不再需要继续执行,可以使用 terminate() 方法终止 Worker。在 Worker 内部,也可以使用 self.close() 来主动关闭 Worker。这两者之间的区别在于,terminate() 会强制结束 Worker,且不会触发正常的关闭事件,而 close() 是在 Worker 自己主动结束时调用的。
例如,在 Worker 中:
if (someCriticalError) {
self.close(); // 关闭 Worker
}
在主线程中:
worker.terminate(); // 强制终止 Worker