# 基于blob对象动态封装一个web worker
# 前言
在html5出来以后, 有许多新特性值得我们关注, 其中一个就是web worker.相信如果关心前端发展的同学就算没有使用过web worker也听过这个东西.今天我们就来讲一讲web worker.
# 基本使用
其实, web worker的作用十分简单, 就是可以在后台运行一个js文件, 所以我们在实际使用中可以将一些非常耗时的计算交给web worker去做.但是值得注意的是, 在web worker中是无法拿到window/document/parent对象的, 以我的理解就是, 你可以看作是与该页面完全独立的一个线程.所以如果涉及到大量的dom操作的时候我们是无法靠web worker完成的, 相反, 如果是大量的计算工作, 我们完全可以将其交给web worker来做, 最后将计算的结果返回给我们.说了这么多, 那么我们下面来实际使用一下.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>计数: <output id="result"></output></p>
<button onclick="startWorker()">开始工作</button>
<button onclick="stopWorker()">停止工作</button>
<script>
var w;
function startWorker() {
w = new Worker('./index.js');
w.onmessage = function(event) {
document.getElementById('result').innerHTML = event.data;
}
}
function stopWorker()
{
w.terminate();
}
</script>
</body>
</html>
我们创建一个worker对象, 将一个js文件作为参数传递给它.注意, worker不接受file对象, 所以我们需要在本地搭一个简单的服务器来运行这个例子.下面的index.js中的内容
var i=0;
function timedCount()
{
i=i+1;
postMessage(i);
setTimeout("timedCount()",500);
}
timedCount();
我们点击开始工作以后, index.js就会在后台运行, 然后通过postMessage给主线程传递数据.而我们通过onmessage来指点监听函数, 获取子线程传递回来的数据, 并显示到页面上.更加具体的教程可以参考阮一峰的博客 (opens new window).
# 深入思考
你看完这个例子之后是不是会觉得这玩意非常简单, 对, 是非常简单.那么我们再来实践一下.我们之前的延迟时间是500ms, 我们现在想变成1000ms.那简单, 我们去index.js中把代码改一下.但是如果在实际生产中, 我们只能运行一个写死的js文件, 那我觉得这个东西是毫无意义的, 所以我们需要想办法动态创建一个js文件, 然后交给webworker去执行.
# 动态创建Web Worker
不熟悉Blob对象和URL.createObjectURL的同学可以先去搜一下他们的概念以及使用方式, 这里我们就用它们来实现我们想要的效果.话不多说, 我们直接上代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function demo() {
console.log('isok');
}
let blob = new Blob([demo.toString() + ' demo()'], {type: 'text/javascript'});
let worker = new Worker(URL.createObjectURL(blob));
</script>
</body>
</html>
这里我们先定义了一个demo函数, 这就是我们后面想要运行的函数.随后将这个函数作为参数传新建一个blob对象.在将blob对象作为createObjectURL的参数, 最后将结果作为web worker的参数.打开页面后, 我们来看看控制台的输出:
nice!我们已经动态创建了一个函数并用web worker去运行他, 下一步我们将其封装成一个函数, 方便我们调用.
class MyWorker {
constructor(f, cb) {
this.f = f;
this.worker = null;
this.onemessage = cb;
}
start() {
const blob = new Blob([`${this.f.toString()} ${this.f.name}()`], { type: 'text/javascript'});
const url = URL.createObjectURL(blob);
this.worker = new Worker(url);
this.worker.onmessage = (event) => this.onemessage(event.data)
}
end() {
this.worker.terminate();
}
}
我们定义一个MyWorker, 传递的参数是要执行的函数和接受参数的函数, 这样我们就可以在外部定义一个函数, 然后将其作为参数传给worker, 实现一个动态的web worker.我们来看看效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="./MyWorker.js"></script>
<title>Document</title>
</head>
<body>
<script>
function demo() {
var i = 0;
function f() {
postMessage(i);
i++;
setTimeout(f, 500);
}
f();
}
let worker = new MyWorker(demo, function(data) {console.log(data)});
worker.start();
setTimeout(() => worker.end(), 2000)
</script>
</body>
</html>
可以看到这就是我们想要的结果.
# 结语
本篇文章只是为如何创建一个动态的web worker提供了一个思路, 可以看到, 我们通过blob和URL.createObjectURL就可以动态创建一个web worker, 这比我们直接运行一个写死的文件要灵活许多.同时对我而言我目前还没有遇见web worker的实际应用场景, 等自己什么时候遇见了需要动态使用web worker使用的应用场景, 再来对其进行一个比较透彻的研究.