由于js 都是单线程的,一旦有大量的js在运行的时候,界面上的ui渲染就会出现卡顿现象,这样让用户感觉体验不好。web worker 可以让js 变得有多线程的特点, 可以用来执行大量与页面无关的js,而不影响界面的渲染。
1、web worker
Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用XMLHttpRequest执行 I/O (尽管responseXML和channel属性总是为空)。一旦创建, 一个 worker 可以将消息发送到创建它的 JavaScript 代码,通过将消息发布到该代码指定的事件处理程序
2、特点
同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息postMessage完成。
脚本限制
Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
3、类型
| 方面 | Dedicated Worker | Shared Worker | Service Worker |
|---|---|---|---|
| 定义 | 一个专用 worker 仅仅能被生成它的脚本所使用 | 一个共享 worker 可以被多个脚本使用 | 一般作为 web 应用程序、浏览器和网络(如果可用)之前的代理服务器 |
| 用途 | 在属于创建者的worker线程中执行任务 | 在同一主域跨browser环境下(window,iframe..)的worker线程执行任务 | 缓存、推送、离线模式等 |
| 执行上下文 | DedicatedWorkerGlobalScope | SharedWorkerGlobalScope | ServiceWorkerGlobalScope |
| 与主线程通讯 | Worker.postMessage() | Worker.port.postMessage() | 特殊生命周期+事件驱动 |
| 访问限制 | DOM API | DOM API | DOM API & 同步API(localStorage, XHR…) |
4、专用web worker
创建专用web worker 非常简单,通过将要在后台线程中运行的脚本文件的 URL 传递给构造函数来完成的
const worker = new Worker('worker.js');
然后通过消息通讯,就可以处理大量无关页面的js代码。直接在work.js 中,监听消息和发送消息
self.addEventListener('message', function(event) {
if (event.data === '消息') {
self.postMessage('发送消息');
}
});
主线程创建的 Worker 对象worker,常用属性和方法:
- worker.onerror:指定 error 事件的监听函数。
- worker.onmessage:指定 message 事件的监听函数,发送过来的数据在
Event.data属性中。 - worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
- worker.postMessage():向 Worker 线程发送消息。
- worker.terminate():立即终止 Worker 线程。
在work.js 中 常用属性和方法,通过self引用自己:
- self.name: Worker 的名字。该属性只读,由构造函数指定。
- self.onmessage:指定
message事件的监听函数。 - self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
- self.close():关闭 Worker 线程。
- self.postMessage():向产生这个 Worker 线程发送消息。
- self.importScripts():加载 JS 脚本。
在web worker 中的postMessage
- 1、可以简单传递数据,数据是
拷贝的(传递给 Worker 的对象需要经过序列化,接下来在另一端还需要反序列化)。 - 2、还有一种
性能更高的方法来传递数据,就是通过可转让对象将数据在主页面和Worker之间进行来回穿梭。可转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝操作。这意味着当传递大数据时会获得极大的性能提升。和按照引用传递不同,一旦对象转让,那么它在原来上下文的那个版本将不复存在。该对象的所有权被转让到新的上下文内。例如,当你将一个ArrayBuffer对象从主应用转让到Worker中,原始的ArrayBuffer被清除并且无法使用。它包含的内容会(完整无差的)传递给 Worker 上下文(二进制数据有:File、Blob、ArrayBuffer等类型)
5、专用web worker 案例
没有使用web woker,存在大量js 运算,导致页面有点不丝滑
<div class="x">
<div class="horizontal"></div>
<div class="vertical"></div>
</div>
<style>
.x {
position: absolute;
transform: rotate(45deg);
top: 60px;
left: 20px;
}
.x .horizontal {
width: 45px;
height: 5px;
position: absolute;
left: -20px;
background: red;
}
.x .vertical {
height: 45px;
width: 5px;
background: red;
position: absolute;
left: 0px;
top: -20px;
}
</style>
<script>
let ele = document.createElement('h3');
ele.innerHTML = 'Computations Complete: 0';
document.body.appendChild(ele);
let counter = 0;
function pause(ms) {
let time = new Date();
counter += 1;
while ((new Date()) - time <= ms) {
// waiting ...
}
}
document.addEventListener('mousemove', function () {
let cross = document.getElementsByClassName('x')[0];
cross.style.left = String(event.clientX) + 'px';
cross.style.top = String(event.clientY) + 'px';
pause(1000);
ele.innerHTML = 'Computations Complete: ' + String(counter);
});
// for touch screens
document.addEventListener('touchmove', function () {
let cross = document.getElementsByClassName('x')[0];
cross.style.left = String(event.touches[0].clientX) + 'px';
cross.style.top = String(event.touches[0].clientY) + 'px';
pause(1000);
ele.innerHTML = 'Computations Complete: ' + String(counter);
});
</script>
使用web worker ,会导致页面流畅
<div class="x">
<div class="horizontal"></div>
<div class="vertical"></div>
</div>
<style>
.x {
position: absolute;
transform: rotate(45deg);
top: 60px;
left: 20px;
}
.x .horizontal {
width: 45px;
height: 5px;
position: absolute;
left: -20px;
background: red;
}
.x .vertical {
height: 45px;
width: 5px;
background: red;
position: absolute;
left: 0px;
top: -20px;
}
</style>
<script type="text/plain" id="workerJS">
let counter = 0;
const pause = function (ms) {
let time = new Date();
counter += 1;
while ((new Date()) - time <= ms) {
// waiting ...
}
}
self.addEventListener('message', function(event) {
if (event.data === 'Compute') {
pause(1000);
self.postMessage(counter);
}
});
</script>
<script>
let ele = document.createElement('h3');
ele.innerHTML = 'Computations Complete: 0';
document.body.appendChild(ele);
let workerJS = document.getElementById('workerJS').innerText;
let blob = new Blob([workerJS]);
let blobURL = window.URL.createObjectURL(blob);
const worker = new Worker(blobURL);
document.addEventListener('mousemove', function () {
let cross = document.getElementsByClassName('x')[0];
cross.style.left = String(event.clientX) + 'px';
cross.style.top = String(event.clientY) + 'px';
worker.postMessage('Compute');
});
// for touch screens
document.addEventListener('touchmove', function () {
let cross = document.getElementsByClassName('x')[0];
cross.style.left = String(event.touches[0].clientX) + 'px';
cross.style.top = String(event.touches[0].clientY) + 'px';
worker.postMessage('Compute');
});
worker.addEventListener('message', function () {
ele.innerHTML = 'Computations Complete: ' + String(event.data);
});
</script>
6、应用
-
数学运算
-
图像、影音等文件处理
-
大量数据检索
比如用户输入时,我们在后台检索答案,或者帮助用户联想,纠错等操作
-
耗时任务都可以放到 webworker
参考文献: MDN Web Worker, 阮一峰Web Worker, web woker应用, Web Worker 提升渲染性能, 浅谈HTML5 Web Worker