最近在看 useWorker 库的时候发现该 hook 只需传入函数即可运行,虽然说 worker 不经常使用,但是就酱紫直接用不需要一个 js 文件承接?

来看看 MDN 中如何创建一个 Worker

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

再看看 useWorker 如何使用

useWorker Usage

import React from 'react';
import { useWorker } from '@koale/useworker';

const numbers = [...Array(5000000)].map((e) => ~~(Math.random() * 1000000));
const sortNumbers = (nums) => nums.sort();

const Example = () => {
const [sortWorker] = useWorker(sortNumbers);

const runSort = async () => {
const result = await sortWorker(numbers); // non-blocking UI
console.log(result);
};

return (
<button type="button" onClick={runSort}>
Run Sort
</button>
);
};

源码解析与最简实现

Demo

请打开控制台查看差异与结果~

最简实现

提取了下 useWorker 的大致逻辑,简化实现了下~主要看下核心逻辑,”凭空”创建 worker

// create worker
const createWWorker = (fn) => {
console.log('worker run');
let promise = {};
const jobRunner = (options) => (e) => {
const [userFuncArgs] = e.data;
return Promise.resolve(options.fn(...userFuncArgs))
.then((result) => {
postMessage(['SUCCESS', result]);
})
.catch((error) => {
postMessage(['ERROR', error]);
});
};

const blobCode = `
onmessage=(${jobRunner})({
fn: (${fn}),
})
`;

const blob = new Blob([blobCode], { type: 'text/javascript' });

const jsUrl = URL.createObjectURL(blob);

const worker = new Worker(jsUrl);

worker.addEventListener('message', (e) => {
const [status, result] = e.data;

switch (status) {
case 'SUCCESS': {
promise.resolve(result);
break;
}
default:
promise.reject(result);
break;
}

if (jsUrl) {
worker.terminate();
URL.revokeObjectURL(jsUrl);
promise = {};
}
});

const runWorker = (...restArgs) =>
new Promise((resolve, reject) => {
promise.resolve = resolve;
promise.reject = reject;
worker.postMessage([restArgs]);
});

return runWorker;
};

// usage
const worker = createWWorker(bubbleSort);
const result = await worker(bigNumbers);

看看如何通过代码生成 url 传给 worker

const blobCode = `
onmessage=(${jobRunner})({
fn: (${fn}),
})
`;

const blob = new Blob([blobCode], { type: 'text/javascript' });

const jsUrl = URL.createObjectURL(blob);

const worker = new Worker(jsUrl);

第一段看起来可能比较疑惑,函数 toString 后放入到 script 里也可以运行?试试就逝世~复制下面代码到控制台。

好吧,script type=”text/javascript“ 忘记这个了,函数 toString 后,比如闭包啊 this 指向等,可能都会有点问题,建议为纯函数~

const script = document.createElement('script');
const script = document.createElement('script');
script.innerText = `const test = 333; document.addEventListener('click', () => {
console.log(test);
})`;
document.body.appendChild(script);

通过生成 Blob 创建资源 Url

第一段生成 Blob 流,再通过流来创建 URL 当作 worker 的文件,思路清晰我是菜 B ~还得学~

const blob = new Blob([blobCode], { type: 'text/javascript' });

const jsUrl = URL.createObjectURL(blob);

区分 Web Socket、Web Worker 和 Service Worker

可能接触不多,往往会弄混或遗忘这些方法~简单描述下~

Web Socket

WebSocket是基于 TCP 的一个全双工通信协议,HTML5 的新特性。
在此之前,客户端和服务端通信的时候,在客户端发送请求之后,服务端才能有所应答。如果服务器有新数据的话不能主动发至客户端,只能客户端进行:

  • 轮询:客户端不断发送新请求给服务端,服务端在应答一次后就断开连接,需不断发起新请求和新连接,造成资源的浪费。
  • 长轮询:客户端发送请求后,不关闭连接,等服务端有期望数据返回后关闭连接。弊端在于服务端需要保持连接,造成资源浪费
    WebSocket 的好处就是在于服务器可以主动发送资源到客户端,且只需一次连接,减少资源损耗。连接后,服务端可以不断的发送数据给客户端,之间无需再连接。

Web Worker

由于 JavaScript 为单线程,在计算一些密集型或高延性的任务时,会影响到整个页面的运行,WebWorker 应声而出。

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
引用自阮一峰老师的文章《Web Worker 使用教程》

Service Worker

Service Worker 实际上是浏览器与服务器之间的代理服务器,它最大的特点是在页面注册并成功安装后,运行于浏览器后台,不受页面刷新的影响,可以拦截作用域范围内所有的 Http 请求。

service worker

  • 安装(Installing)
    在浏览器加载了运用 Service Worker 的页面时,Service Worker 的 JS 文件也会被保存下来,然后运行安装,下载相应缓存。安装完成后,就会激活 Service Worker(Activated)
  • 激活(Activated)
    激活 Service Worker 后,Service Worker开始控制页面后台,当您刷新页面或者发送请求的时候,Service Worker 会对请求进行拦截,然后进行相应的操作(一般是看是否命中缓存,如果命中,就直接返回缓存,不需要再发送请求)

Service Worker在浏览器运行的时候,每隔一段时间,它会在后台尝试重新下载Service Worker的JS文件,只要新的JS文件和旧的有一点不相同,那么就会重新安装-激活。BTW,Service Worker也是Web Work的一种。