带你了解Web Worker - 实操篇

2,482 阅读4分钟

1-Web Worker 实际操作步骤

Dedicated Worker

接下来我们开始讲解一个使用 Web Worker 运算斐波那契数列的例子。项目地址

首先,我们需要通过Worker()构造器创建worker实例,其只接受一个参数,该参数则是线程脚本的地址,该地址必须符合同源策略。

可以查看代码,我再使用同目录下的 dedicate-worker.js 文件创建了一个 Worker 对象出来,当初始化完成后,浏览器后台线程就回运行该脚本了。

var worker = new Worker('dedicate-worker.js');

那主线程(页面)和后台线程的 Worker 之间是如何通信的呢?这里就要 postMessage 方法了。

worker.postMessage(message, [transfer]);

postMessage 方法接受两个参数,第一个就是 worker 之间传递的数据信息,第二个参数是一个数组,用来转让对象所有权。

/* html 部分
<div class='normal'>
    <button onclick='normalPost(34)'>34</button>
    <button onclick='normalPost(42)'>42</button>
</div> 
*/
// js 部分
function normalPost(num) {
    worker.postMessage(num);
}

上述代码中,当我们点击对应的按钮时候,会发送数字到对应的到子线程。后台子进程需要监听 onmessage 事件,才能接受主进程发送来的数据。而该事件接受一个 MessageEvent 对象,其中 data 属性就是主进程传入的数据。

// dedicate-worker.js
// 斐波那契数列
function fabonacci(n) {
    if (n === 0) {
        return 0;
    }
    if (n === 1) {
        return 1;
    }
    return fabonacci(n - 1) + fabonacci(n - 2);
}

onmessage = function(messageEvent) {
    var str = messageEvent.data;
    str = typeof str === 'number' ? String(str) : str;
    switch (str) {
	    //  ...
        case (str.match(/[0-9]/) || {}).input:
            var result = fabonacci(Number(str));
            postMessage({msg: str+'的斐波那契数列是:' + result, code: 'fobo'});
            break;
        default:
            postMessage({msg: 'hi, bro~ 慢慢想一下~', code: ''});
            break;
    }
}

上述代码中,我们通过 onmessage 监听获取主线程传输进来的数据,如果对应的数据运算完成过后,直接调用 postMessage 方法将对应的结果传输出去,注意在子进程的脚本中,因为全局作用域已经改变的原因是可以直接调用 postMessage 的方法,但是在主进程中是不可以的,是需要指定对应的wroker对象调用的。

回到主进程代码,在这里我们也只要让对应的 worker 监听 onmessage 事件即可接受由子线程传递过来的数据。

worker.addEventListener('message', (e) => {
    var res = e.data;
    switch (e.data.code) {
        default:
            app.textContent = res.msg;
            break;
    }
    // ...
});

因为 Worker 还是比较消耗资源的,所以当没有用的时候,可以选择关闭,即调用 terminate 方法即可。

var worker = new Worker('dedicate-worker.js');
worker.terminate();

shared Worker

我们实现一个多个 tab 共享部分数据效果。大概这样的效果:

多个tab共享文字效果图

Shared Worker 创建的方法类似于 dedicated Worker,调用 SharedWorker 构造器,语法如下:

new SharedWorker(aURL, name);

其中 aURL 表示脚本的地址,name 表示子线程的名字,同名的线程是可以共享的噢,但依旧要遵循同源策略。在我们的项目中,我们是这样创建的:

var shareWorker = new SharedWorker('share-worker.js', 'sharedWork');

同样的我们可以通过 postMessage 方法将信息传递给子进程,但因为 SharedWorker 实现的原因不同,我们需要通过实例的 port 属性去调用 postMessage。

input.addEventListener('change', (e) => {
    shareWorker.port.postMessage({value: e.target.value, type: 'write'});
})

上述代码中,我们通过监听输入框的 change 事件触发主线程传递信息给子线程。而子线程因为作用域的不同,对应的连接主线程方式稍有不同,我们通过监听 onconnect 接通主线程,然后通过接受参数的 ports 属性获取到 MessagePort 对象,这时候便可以使用 onmessage 和 postMessage 处理和传递数据了。

// ...
onconnect = function (messageEvent) { 
    messageEvent.source.addEventListener('message', (event) => {
            swicthByTypeCode(event.data);
    })
    source.start();
}
// ...

上述代码,主要是子线程连接主线程,并让 MessagePort 对象监听 message 事件,但注意若使用 addEvevntListener 监听的时候,需要自行 start 方法启动。

调试

调试 Dedicated Worker 可以直接在控制台进行调试,唯独调试 SharedWorker 的时候需要进入 chrome://inspect/#workers 点开 inspect 进行调试。

对应的控制台展示如下:

看到了最熟悉的界面,就可以 debugger 调试了。上述仅仅描述了简单的用法,并无涉及某些高级用法,如 importScripts 的应用、错误处理机制等。其实上文有不断的提及到 Worker 的作用域和 window 的作用域是不同的,那接下来我们看看他们到底是什么样子哩?