# 基于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使用的应用场景, 再来对其进行一个比较透彻的研究.