日期:2021年12月1日标签:JavaScript

Web Workers的简单介绍 #

"JavaScript是单线程的",Js的开发这们一直这样认为。但是今天要介绍的是JavaScript中的多线程编程,你没有听错,我说的就是多线程编程——Web Workers

一. 前言 #

Web Workers是浏览器(宿主环境)的一个功能,并不是JavaScript的标准功能,那么它有什么作用呢?

假设你在页面上点击一个按钮,然后会触发执行一系列密集型任务,需要耗费几十秒甚至几分钟执行完毕,那么此时UI会处于卡死状态,导致Page Unresponsive错误。你肯定不希望这种事情发生,这会导致很不好的用户体验。

你希望有一种方式,可以让主线程继续渲染UI,而将密集型任务的处理交给另一个子线程取负责,等子线程处理完毕后,主线程可以获得子线程的处理结果并作出反应。这就是Web Workers出现的意义。

你可以在主线程创建一个Web worker,将一些耗时的任务交给Web worker处理,Web worker处理任务完毕,将结果返回给主线程即可。这样就不会导致Page Unresponsive这种情况出现。

Web Workers机制与C++、C#其他语言中的多线程不同的是子线程(Web worker)不能与其他线程(主线程)共享内存。这是一个优点,共享内存意味着共享作用域和资源,如果共享内存,那么你将遇到其他多线程语言(C++、C#等)要面对的所有问题,比如合作式或抢占式的锁机制(mutex等),那就太麻烦了。

Worker线程一旦新建成功,它会始终运行,所以在使用完毕后,就应该关闭,否则占用资源过度。

Web Worker有以下几个使用注意点。

(1)同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM 限制

Worker线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM对象,也无法使用documentwindowparent这些对象。但是,Worker线程可以读取navigator对象和location对象。

(3)通信联系

Worker线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成

(4)脚本限制

Worker线程不能执行alert()方法和confirm()方法,但可以使用XMLHttpRequest对象发出AJAX请求。

(5)文件限制

Worker线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

二. 用法 #

1. 主线程 #

在主线程内新建Worker线程。

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

Worker()构造函数的参数是一个脚本文件,脚本文件是Worker线程的执行代码。

主线程与Worker通过message通信,调用worker.postMessage()方法向Worker发送消息,worker在接收到主线程的消息,可以根据消息内容处理任务(具体如何根据消息处理任务,在后面会讲解)。

worker.postMessage({data: "Web worker tutorial"}); 

postMessage()的参数可以是任意类型的数据,包括二进制。

主线程可以通过worker监听message事件接收子线程的消息,通过event.data获取数据,与其他事件处理方式类似。

有两种方式,onmessage()或者addEventListener()

(1)onmessage()

worker.onmessage = receivedWorkerMessage;

function receivedWorkerMessage(event) {
    var message = event.data; // 消息内容

    ...
}

(2)addEventListener()

worker.addEventListener("message", receivedWorkerMessage);

function receivedWorkerMessage(event) {
    var message = event.data; // 消息内容

    ...
}

Worker完成任务后,主线程通过worker.terminate()关闭worker。

2. Worker子线程 #

主线程通过worker.postMessage触发message事件,向worker线程发送消息。Worker通过监听message事件,可以接受主线程发送的消息。

子线程中的全局对象为self关键字,不是window。worker的message事件与其他dom事件一样,所以可以有几种方式监听事件。

(1)使用self.addEventListener()

self.addEventListener('message', function (event) {
    const message = event.data;
    
    ...根据消息内容,进行一系列任务(通常是耗时的任务)

};, false);

// self为全局对象所以可以省略
addEventListener('message', function (event) {
    const message = event.data;
    
    ...根据消息内容,进行一系列任务(通常是耗时的任务)

};, false);

(2)使用self.onmessage()

self.onmessage = function (event) {
    const message = event.data;
    
    ...根据消息内容,进行一系列任务(通常是耗时的任务)
};

// self为全局对象所以可以省略
self.onmessage = function (event) {
    const message = event.data;
    
    ...根据消息内容,进行一系列任务(通常是耗时的任务)
};

3. Worker可以加载脚本 #

Worker可以通过importScripts()加载脚本。

// 加载单个脚本
importScripts('script1.js');

// 加载多个脚本
importScripts('script1.js', 'script2.js');

4. 监听错误 #

主线程可以监听错误,如果发生错误,Worker会触发error事件。

(1)worker.onerror()

worker.onerror = workerError;

function workerError(error) {
    ...
}

(2)worker.addEventListener

worker.addEventListener("error", workerError);

function workerError(error) {
    ...
}

5. worker完成使命,记得关闭 #

// 主线程
worker.terminate();

// Worker 线程
self.close(); // self可以省略

三. 一个案例 #

下面是一个在指定数字范围内,查找所有的素数的demo,比较耗费时间,所以查找计算的过程放到了Worker中处理。你可以点击Start Searching按钮查看运行效果。

附:参考资料

(完)

目录