Web Worker总结

  1. 前言

  1. js是单线程的,不允许多线程操作DOM,当渲染较大的文件时,主线程渲染时间会大大加长,页加载时间过长,加用户体验差

  1. Web Worker的出现时使js有了处理多线程的能里力,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。

  1. 常用的window对象和document对象无法使用,location和navigator对象可以以可读方式访问。绝大多数 Window 对象上的方法和属性,都被共享到 Worker 上下文全局对象 WorkerGlobalScope 中。同样,Worker 线程上下文也存在一个顶级对象 self

2. 使用

  1. 创建worker
  • 创建 worker 只需要通过 new 调用 Worker() 构造函数即可,它接收两个参数

const worker = new Worker(path, options);
2. js 主线程与 worker 线程数据传递
  • 主线程与 worker 线程都是通过 postMessage 方法来发送消息,以及监听 message 事件来接收消息,postMessage() 方法接收的参数可以是字符串、对象、数组等。

  • 不能传递的有Error 以及 Function 对象;DOM 节点,RegExp 对象

  • 主线程与 worker 线程之间的数据传递是传值而不是传地址。所以你会发现,即使你传递的是一个Object,并且被直接传递回来,接收到的也不是原来的那个值了。

// main.js(主线程)
const myWorker = new Worker('/worker.js'); // 创建worker
const obj = {name: '小明'};

myWorker.addEventListener('message', e => { // 接收消息
   console.log(e.data === obj); // false      worker线程发送的消息
});

myWorker.postMessage(obj); // 向 worker 线程发送消息,对应 worker 线程中的 e.data
// worker.js(worker线程)
self.addEventListener('message', e => { // 接收到消息
        self.postMessage(e.data); // 向主线程发送消息
});
3. 监听错误信息
  • web worker 提供两个事件监听错误,error 和 messageerror。这两个事件的区别是:

myWorker.addEventListener('messageerror', err => {
    console.log(err.message)
});

self.addEventListener('messageerror', err => {
    console.log(err.message);
});
4. 关闭 worker 线程
  • worker 线程的关闭在主线程和 worker 线程都能进行操作,但对 worker 线程的影响略有不同。

// main.js(主线程)
const myWorker = new Worker('/worker.js'); // 创建worker
myWorker.terminate(); // 关闭worker
// worker.js(worker线程)
self.close(); // 直接执行close方法就ok了
  • 在主线程关闭 worker,主线程与 worker 线程之间的连接立刻停止,即使有待执行的任务继续调用 postMessage() 方法,但主线程不会再接收到消息。

  • 在 worker 线程关闭 worker,不会直接断开与主线程的连接,而是等 worker 线程当前的 Event Loop 所有任务执行完,再关闭。也就是说,在当前 Event Loop 中继续调用 postMessage() 方法,主线程还是能通过监听message事件收到消息的。

5. Worker 线程引用其他js文件

有时候worker 进程处理的任务很复杂,不需要把所有代码都塞到 worker.js 里,可以在 worker 线程中利用 importScripts() 方法加载我们需要的js文件,通过此方法加载的js文件不受同源策略约束

// utils.js
const add = (a, b) => a + b;
// worker.js(worker线程)
// 使用方法:importScripts(path1, path2, ...); 

importScripts('./utils.js');

console.log(add(1, 2)); // log 3
6. ESModule 模式
  • 有时在导入js文件时发现, importScripts() 方法执行失败。因为 js 文件用的是 ESModule 模式。初始化 worker 时的第二个可选参数,我们可以直接使用 module 模式初始化 worker 线程!

// main.js(主线程)
const worker = new Worker('/worker.js', {
    type: 'module'  // 指定 worker.js 的类型
});
// utils.js
export default add = (a, b) => a + b;
// worker.js(worker线程)
import add from './utils.js'; // 导入外部js

self.addEventListener('message', e => { 
    postMessage(e.data);
});

add(1, 2); // log 3

export default self; // 只需把顶级对象self暴露出去即可

3. SharedWorker

  • SharedWorker 是一种特殊类型的 Worker,可以被多个浏览上下文访问,比如多个 windows,iframes 和 workers,但这些浏览上下文必须同源

  • SharedWorker 线程的创建和使用跟 worker 类似,事件和方法也基本一样。不同点在于,主线程与 SharedWorker 线程是通过MessagePort建立起链接,数据通讯方法都挂载在SharedWorker.port上。

  • 值得注意的是,如果你采用 addEventListener 来接收 message 事件,那么在主线程初始化SharedWorker() 后,还要调用 SharedWorker.port.start() 方法来手动开启端口。

// main.js(主线程)
const myWorker = new SharedWorker('./sharedWorker.js');

myWorker.port.start(); // 开启端口

myWorker.port.addEventListener('message', msg => {
    console.log(msg.data);
})
  • 如果采用 onmessage 方法,则默认开启端口,不需要再手动调用SharedWorker.port.start()方法

4. 案例

index 页面的 add 按钮,每点击一次,向 sharedWorker 发送一次 add 数据,页面 count 增加1

// index.html
    <body>
        <p>index page: </p>
        count: <span id="container">0</span>
        <button id="add">add</button>
        <br>
        // 利用iframe加载
        <iframe src="./iframe.html"></iframe>
    </body>
    <script type="text/javascript">
        if (!!window.SharedWorker) {
            const container = document.getElementById('container');
            const add = document.getElementById('add');
            
            const myWorker = new SharedWorker('./sharedWorker.js');
            
            myWorker.port.start();

            myWorker.port.addEventListener('message', msg => {
                container.innerText = msg.data;
            });

            add.addEventListener('click', () => {
                myWorker.port.postMessage('add');
            });
        }
    </script>

iframe 页面的 reduce 按钮,每点击一次,向 sharedWorker 发送一次 reduce 数据,页面count 减少

// iframe.html
    <body>
        <p>iframe page: </p>
        count: <span id="container">0</span>
        <button id="reduce">reduce</button>
    </body>
    <script type="text/javascript">
        if (!!window.SharedWorker) {
            const container = document.getElementById('container');
            const reduce = document.getElementById('reduce');

            const myWorker = new SharedWorker('./sharedWorker.js');

            myWorker.port.start();
            
            myWorker.port.addEventListener('message', msg => {
                container.innerText = msg.data;
            })

            reduce.addEventListener('click', () => {
                myWorker.port.postMessage('reduce');
            });
        }
    </script>

sharedWorker 在接收到数据后,根据数据类型处理 num 计数,然后返回给每个已连接的主线程

// sharedWorker.js

let num = 0;
const workerList = [];

self.addEventListener('connect', e => {
    const port = e.ports[0];
    port.addEventListener('message', e => {
        num += e.data === 'add' ? 1 : -1;
        workerList.forEach(port => { // 遍历所有已连接的part,发送消息
            port.postMessage(num);
        })
    });
    port.start();
    workerList.push(port); // 存储已连接的part
    port.postMessage(num); // 初始化
});

5. 调试

  • 在 sharedWorker 线程里使用 console 打印信息,不会出现在主线程的的控制台中。如果你想调试 sharedWorker,需要在 Chrome 浏览器输入 chrome://inspect/ ,这里能看到所有正在运行的 sharedWorker,然后开启一个独立的 dev-tool 面板。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值