web worker
TIP
Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。 也就是前端得多线程。应用场景在大数据需要计算,或者多个模块需要计算、统计等。可以把每个小模块都拆分成一个web worker
线程。(例如:项目本来6个模块,每个模块计算需要6s,合计就是6 * 6 = 36s
了,假如我们拆分成6个web worker
分别执行。那就是1s
左右就能返回结果了,不会阻塞主线程,是不是时间上提高了很多, 对用户体验也提升了很多)。
worker 特性检测
TIP
为了更好的错误处理控制以及向下兼容,将你的 worker 运行代码包裹在以下代码中是一个很好的想法
if (window.Worker) {
console.log('support web worker')
} else {
console.log('no support web worker')
}
使用web worker
TIP
使用new Worker()
创建一个web worker
, 入参可以是数据、函数...。 个人理解就是把数据或者传到webworker
里面去执行, 他里面得js代码是局部(封闭)得,就是需要用传参得方式去进行数据交互。
示例:
// index.html
const worker = new Worker('./index.js');
worker.onmessage = function (event) { // 外部接受返回得数据
console.log(event, 'web worker success response')
}
worker.onerror = function (event) {
console.log(event, '[error]: web worker error response')
}
worker.postMessage('主线程向web工作线程发送得消息'); // 使用postMessage 向web worker 发送消息
// index.js 也就是我web Worker传进去得index.js路劲
// 以下是index.js代码
this.onmessage = function (event) { // 在web worker 线程使用onmessage获取到主线程发送得消息
// 工作线程收到主线程的消息
console.log(event, 'event')
this.postMessage({ // 使用this.postMessage 可以向主线程发送消息
value: '工作线程向主线程发送消息'
});
};
TIP
上面就是一个简单得web worker 示例了(需要使用vs code得 Live Server就是对index.html起一个服务不然会提示你路径加载错误找不到index.js)。 使用new Worker()
创建web 线程, 在工作线程和主线程用onmessage()
方法做监听是否使用postMessage()
通信。
web Worker应用
TIP
上面咱们看到了怎么使用web worker
,那咱们能把web worker
应用在那个场景呢? 比如:我需要随机生成5次 1000000个数字,并排序得到排序后得数据。
// index.html
function createdWorker() {
const worker = new Worker('./index.js');
worker.onmessage = function (event) { // 外部接受返回得数据
console.log(event, 'web worker success response')
}
worker.onerror = function (event) {
console.log(event, '[error]: web worker error response')
}
worker.postMessage('执行1'); // 使用postMessage 向web worker 发送消息
}
createdWorker(); // 调用1次
createdWorker(); // 调用2次
createdWorker(); // 调用3次
createdWorker(); // 调用4次
createdWorker(); // 调用5次
// index.js代码
function computedCallback() { // web worker 计算方法
const randomVal = Math.random() * 100;
const data = [];
for (let i = 0; i < 1000000; i++) {
data.push(Math.random() * 1000)
}
return data.sort((a, b) => a - b)
}
this.onmessage = function (event) { // 在web worker 线程使用onmessage获取到主线程发送得消息
// 工作线程收到主线程的消息
console.log(event, 'event')
this.postMessage({ // 使用this.postMessage 可以向主线程发送消息
value: computedCallback(),
});
};
TIP
经过上面得实验发现几乎是web worker
几乎是同一时间返回得结果(原来得1/5时间),我也用不使用得web worker
测试了(需要加载很久)下面是使用原来js写的都是统计100000个数字进行排序(给我卡的就想直接关浏览器)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script>
// index.js 监听消息
function computedCallback() { // web worker 计算方法
const randomVal = Math.random() * 100;
const data = [];
for (let i = 0; i < 1000000; i++) {
data.push(Math.random() * 1000)
}
return data.sort((a, b) => a - b)
}
console.log(computedCallback(), 1)
console.log(computedCallback(), 2)
console.log(computedCallback(), 3)
console.log(computedCallback(), 4)
console.log(computedCallback(), 5)
</script>
总结:
可以看的出使用web worker
得好处了吧,不阻塞线程并差不多在同一时间返回结果。
web worker 销毁
TIP
在我使用完需要销毁掉,我下次需要用到在重新创建就好了 不用一直建立一个web worker
工作线程。用之创建,用完销毁。
- 在主线程销毁创建得
web worker
,使用terminate()
方法。还以上面代码为例,可以在控制台开发者模式得Sources查看到是否进程被销毁
// index.html
function createdWorker() {
const worker = new Worker('./index.js');
worker.onmessage = function (event) { // 外部接受返回得数据
console.log(event, 'web worker success response');
// 在接收到我想要得结果后,就不需要这个线程了。
worker.terminate(); // 终止、销毁
}
worker.onerror = function (event) {
console.log(event, '[error]: web worker error response')
}
worker.postMessage('执行1'); // 使用postMessage 向web worker 发送消息
}
createdWorker();
createdWorker();
createdWorker();
createdWorker();
createdWorker();
- 在
web worker
任务线程使用close
方法进行销毁、关闭。可以在控制台开发者模式得Sources查看到是否进程被销毁
// index.js 任务js
// index.js 监听消息
function computedCallback() { // web worker 计算方法
const data = [];
for (let i = 0; i < 1000000; i++) {
data.push(Math.random() * 1000)
}
return data.sort((a, b) => a - b)
}
this.onmessage = function (event) { // 在web worker 线程使用onmessage获取到主线程发送得消息
// 工作线程收到主线程的消息
console.log(event, 'event');
this.postMessage({ // 使用this.postMessage 可以向主线程发送消息
value: computedCallback(),
});
this.close(); // 新增
};
在项目中使用web Worker
TIP
在vue项目中是否可以使用web worker
呢?当然可以得。在当时调研时发现了一个simple-web-worker 库 他就是对web worker 进行了一系列封装,让你得web worker
更简单。
核心代码
// createDisposableWorker.js
export const createDisposableWorker = response => {
const URL = window.URL || window.webkitURL
const blob = new Blob([response], { type: 'application/javascript' }) // eslint-disable-line
const objectURL = URL.createObjectURL(blob)
const worker = new Worker(objectURL) // eslint-disable-line
worker.post = message =>
new Promise((resolve, reject) => {
worker.onmessage = event => {
URL.revokeObjectURL(objectURL)
resolve(event.data)
}
worker.onerror = e => {
console.error(`Error: Line ${e.lineno} in ${e.filename}: ${e.message}`)
reject(e)
}
worker.postMessage({ message })
})
return worker
}
const makeResponse = work => `
self.onmessage = function(event) {
const args = event.data.message.args
if (args) {
self.postMessage((${work}).apply(null, args))
return close()
}
self.postMessage((${work})())
return close()
}
`
解读
TIP
使用URL.createObjectURL()
创建一个url, 这个url是用传进来得js。就是给index.js
创建一个url提拱给 web worker
去用。 makeResponse
方法就是这个indexjs
的模板。我又基于前面核心代码封了一层代码如下:
// dispatch.js
const makeResponse = (work) => `
self.onmessage = function(event) {
self.postMessage((${work})(event.data));
}
`;
const createDisposableWorker = (response) => {
const URL = window.URL || window.webkitURL;
const blob = new Blob([response], { type: 'application/javascript' });
const objectURL = URL.createObjectURL(blob);
const worker = new Worker(objectURL);
worker.post = (...args) => new Promise((resolve, reject) => {
worker.onmessage = (event) => {
URL.revokeObjectURL(objectURL);
resolve(event.data);
};
worker.onerror = (e) => {
console.error(`[web Worker]: Line ${e.lineno} in ${e.filename}: ${e.message}`);
reject(e);
};
worker.postMessage(...args);
});
return worker;
};
export default class Dispatcher {
constructor(fn) {
this.worker = createDisposableWorker(makeResponse(fn));
}
dispatch(event) {
return this.worker.post(event);
}
dispose() {
this.worker.terminate();
}
}
使用方法
// 引入封装好的 Dispatcher
import Dispatcher from 'dispatch.js'
// 引入 computedCallback函数
import computedCallback from 'computed.worker.js'
const worker = new Dispatcher(computedCallback); // 把方法放入Dispatcher中
worker.dispatch()
.then((res) => {
console.log(res); // 计算好的数据、结果
// 使用dispose 终止web worker
worker.dispose();
});
源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script>
const makeResponse = (work) => `
self.onmessage = function(event) {
self.postMessage((${work})(event.data));
}
`;
const createDisposableWorker = (response) => {
const URL = window.URL || window.webkitURL;
const blob = new Blob([response], { type: 'application/javascript' });
const objectURL = URL.createObjectURL(blob);
const worker = new Worker(objectURL);
worker.post = (...args) => new Promise((resolve, reject) => {
worker.onmessage = (event) => {
URL.revokeObjectURL(objectURL);
resolve(event.data);
};
worker.onerror = (e) => {
console.error(`[web Worker]: Line ${e.lineno} in ${e.filename}: ${e.message}`);
reject(e);
};
worker.postMessage(...args);
});
return worker;
};
class Dispatcher {
constructor(fn) {
this.worker = createDisposableWorker(makeResponse(fn));
}
dispatch(event) {
return this.worker.post(event);
}
dispose() {
this.worker.terminate();
}
}
function computedCallback() { // web worker 计算方法
const randomVal = Math.random() * 100;
const data = [];
for (let i = 0; i < 1000000; i++) {
data.push(Math.random() * 1000)
}
return data.sort((a, b) => a - b)
}
function createdWorker() {
const worker = new Dispatcher(computedCallback); // 创建
worker.dispatch().then((res) => {
console.log('web worker success response', res);
worker.dispose(); // 销毁
})
}
createdWorker();
createdWorker();
createdWorker();
createdWorker();
createdWorker();
</script>
结束语
TIP
其他方式在项目中引入web worker
也都有尝试,总觉得用起来还要配置一些webpack来比较麻烦,个人认为还是用这种方案比较好用。 经过这篇文章相信对web worker
有了一个深刻的认识并且能在项目中应用起来的。