1.前言
为了让大家能够更好地理解和掌握 Web Workers,在正式介绍 Web Workers 之前,我们先来介绍一些与 Web Workers 相关的基础知识。
1.1 进程与线程
进程是一个工厂,它有独立的资源,线程是工厂中的工人,多个工人协作完成任务,工人之间共享工厂内的资源,比如工厂内的食堂或餐厅。 打一个最直白的例子,公司是一个进程,那么你、我、他就是线程。大家共享公司的各种资源。工厂(进程)与工厂(进程)之间是相互独立的。为了让大家能够更直观地理解进程与线程的区别,我们继续来看张图:
由上图可知,操作系统会为每个进程分配独立的内存空间,一个进程由一个或多个线程组成,同个进程下的各个线程之间共享程序的内存空间。相信通过前面两张图,小伙伴们对进程和线程之间的区别已经有了一定的了解。
这里在补充一个更细概念「协程」,协程是一种比线程更加轻量级的存在。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是 A 协程,要启动 B 协程,那么 A 协程就需要将主线程的控制权交给 B 协程,这就体现在 A 协程暂停执行,B 协程恢复执行;同样,也可以从 B 协程中启动 A 协程。通常,如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程。
1.2 单线程与多线程
如果一个进程只有一个线程,我们称之为单线程。单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。单线程处理的优点:同步应用程序的开发比较容易,但由于需要在上一个任务完成后才能开始新的任务,所以其效率通常比多线程应用程序低。
如果完成同步任务所用的时间比预计时间长,应用程序可能会不响应。针对这个问题,我们可以考虑使用多线程,即在进程中使用多个线程,这样就可以处理多个任务。
对于 Web 开发者熟悉的 JavaScript 来说,它运行在浏览器中,是单线程的,每个窗口一个 JavaScript 线程。其实在浏览器内核(渲染进程)中除了 JavaScript 引擎线程之外,还含有 GUI 渲染线程、事件触发线程、定时触发器线程等。因此对于浏览器的渲染进程来说,它是多线程的。
但是 JavaScript 引擎与 GUI 渲染线程是互斥的,如果 JavaScript 引擎执行了一些计算密集型或高延迟的任务,那么会导致 GUI 渲染线程被阻塞或拖慢。那么如何解决这个问题呢?当然是使用本文的主角 ------ Web Workers。
2. Web Workers 是什么?
Web Workers 是 HTML5 标准的一部分,这一规范定义了一套 API,它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。Web Workers 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。
在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,可以在独立线程中处理一些计算密集型或高延迟的任务,从而允许主线程(通常是 UI 线程)不会因此被阻塞或拖慢。
3. Web Workers 的分类
Web Workers 规范中定义了两类工作线程,分别是专用线程 Dedicated Worker 和共享线程 Shared Worker,其中,Dedicated Worker 只能为一个页面所使用,而 Shared Worker 则可以被多个页面所共享。
3.1 Dedicated Worker
先看一个专用线程的简单示例:
js
// index.html
if (window.Worker) {
let worker = new Worker("dw.js");
worker.postMessage("Main Thread -> Worker Thread --- main");
worker.onmessage = (e) => {
console.log(`Main Tread : 接受到消息 - ${e.data}`);
}
} else {
console.log("当前浏览器,不支持 Web Worker");
}
js
// dw.js
self.onmessage = function(e) {
console.log(`Worker Tread : 接受到消息 - ${e.data}`);
postMessage("Worker Thread -> Main Thread --- worker");
}
并且针对每一个 Web Workers 都可以在创建自己的子 Web Workers,实现嵌套的 Web Worker。
js
// index.html
if (window.Worker) {
let worker = new Worker("dw.js");
worker.postMessage("Main Thread -> Worker Thread --- main");
worker.onmessage = (e) => {
console.log(`Main Tread : 接受到消息 - ${e.data}`);
}
} else {
console.log("当前浏览器,不支持 Web Worker");
}
js
// dw.js
self.onmessage = function(e) {
console.log(`Worker Thread: 接受消息 - ${e.data}`);
postMessage("Worker Thread -> Main Thread --- worker");
setTimeout(() => {
let worker = new Worker("sub-dw.js");
worker.postMessage("Worker Thread -> Sub Worker Thread --- worker");
worker.onmessage = (e) => {
console.log(`Worker Tread : 接受到消息 - ${e.data}`);
};
}, 0)
}
js
// sub-dw.js
self.onmessage = function(e) {
console.log(`Sub Worker Thread: 接收消息 - ${e.data}`);
postMessage("Sub Worker Thread -> Worker Thread --- sub worker");
}
注意:由于 Web Worker 有同源限制,所以在进行本地调试或运行以下示例的时候,需要先启动本地服务器,直接使用 file:// 协议打开页面的时候,会抛出异常。
3.2 Shared Worker
Shared Worker 是一种特殊类型的 Worker,可以被多个浏览上下文访问,比如多个 windows,iframes 和 workers,但这些浏览上下文必须同源。相比 dedicated workers,它们拥有不同的作用域。
看一个简单示例:
html
<button id="tab">新开 Tab</button>
<button id="l-btn">点赞</button>
<p><span id="likedCount">还没有人点赞</span></span>👍</p>
<script>
let likedCountEl = document.querySelector("#likedCount");
let worker = new SharedWorker("./sw.js");
console.log('worker.port', worker.port);
worker.port.start();
// 监听消息
worker.port.onmessage = function (val) {
likedCountEl.innerHTML = val.data;
};
document.getElementById('tab').onclick = function () {
// IP 地址为本地起的服务
const windowOpen = window.open('http://127.0.0.1:5500/kitchen-sink/apps/shared-worker/index.html');
}
document.getElementById('l-btn').onclick = function () {
worker.port.postMessage('点赞了');
};
</script>
js
let a = 666;
console.log('shared-worker');
onconnect = function (e) {
const port = e.ports[0];
console.log('shared-worker connect');
// 不能使用这种方式监听事件
// port.addEventListener('message', () => {
// port.postMessage(++a);
// });
port.postMessage(a);
port.onmessage = () => {
port.postMessage(++a);
};
console.log('当前点赞次数:', a);
};
这是一个多 Tab 进行消息通信的例子,这个例子就诠释了 Shared Worker 的能力。但是注意:
- 如果要使 Shared Worker 连接到多个不同的页面,这些页面必须是同源的(相同的协议、host 以及端口)。
- Shared Worker 在实现跨页面通信时的,它无法主动通知所有页面,需要刷新页面或者是定时任务来检查是否有新的消息。在例子中我是手动刷新的,当然可以使用 setInterval 来定时刷新。
- 与常规的 Worker 不同,首先我们需要使用 onconnect 方法等待连接,然后我们获得一个端口,该端口是我们与窗口之间的连接。
3.3 轻量级的 Web Workers,Worklet
Worklet 是一个非常轻量且高度特别的 worker。worklet 与浏览器的渲染管道挂钩,使我们能够对浏览器的渲染过程(例如样式和布局)进行低级访问。
使用 Worklet,可以运行 JavaScript 和 WebAssembly 代码来进行需要高性能的图形渲染或音频处理。但是注意 Worklet 被严格限制用途,不能随意创建。目前有四类 Worklet:PaintWorklet、AudioWorklet、AnimationWorklet 和 LayoutWorklet。
例1:通过 Worklet 实现自定义背景图片。
html
<body>
<style>
div {
width: 100vw;
height: 1000vh;
background: paint(circleBgSet);
--gap: 2;
--color: #cadf44;
--size: 64;
}
</style>
<div></div>
<script>
if ("paintWorklet" in CSS) {
CSS.paintWorklet.addModule("./worklet.js");
}
</script>
</body>
js
// worklet.js
registerPaint(
"circleBgSet",
class {
static get inputProperties() {
return [
"--gap",
"--color",
"--size"
];
}
paint(ctx, size, properties) {
const gap = properties.get("--gap");
const color = properties.get("--color");
const eachSize = properties.get("--size");
const halfSize = eachSize / 2;
const n = size.width / eachSize;
const m = size.height / eachSize;
ctx.fillStyle = color;
for (var i = 0; i < n + 1; i++) {
for (var j = 0; j < m + 1; j++) {
let x = i * eachSize + ( j % 2 === 0 ? halfSize : 0);
let y = j * eachSize / gap;
let radius = i * 0.85;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
}
}
}
}
);
例2:通过 Worklet 实现 mask。
html
<body>
<style>
body, html {
width: 100%;
height: 100%;
display: flex;
}
@property --transition-time {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
div {
width: 300px;
height: 300px;
background: url(https://km.sankuai.com/api/file/cdn/1669599629/38879479131?contentType=1&isNewContent=false);
background-size: cover;
background-position: center center;
margin: auto;
cursor: pointer;
-webkit-mask: paint(maskSet);
--transition-time: 1;
--size-m: 10;
--size-n: 10;
transition: --transition-time 1s linear;
}
div:hover {
--transition-time: 0;
}
</style>
<div></div>
<script>
if ("paintWorklet" in CSS) {
CSS.paintWorklet.addModule("./worklet-mask.js");
}
</script>
</body>
js
registerPaint(
"maskSet",
class {
static get inputProperties() {
return ["--size-n", "--size-m", "--transition-time"];
}
paint(ctx, size, properties) {
const n = properties.get("--size-n");
const m = properties.get("--size-m");
const t = properties.get("--transition-time");
const width = size.width / n;
const height = size.height / m;
console.log('t', t);
for (var i = 0; i < n; i++) {
for (var j = 0; j < m; j++) {
ctx.fillStyle = "rgba(0,0,0," + (t * (Math.random() + 1)) + ")";
ctx.fillRect(i * width, j * height, width, height);
}
}
}
}
);
例3:通过 Worklet 来做白噪声。
html
<body>
<button id="startButton">我开始了.....</button>
<button id="stopButton">开点我结束吧.....</button>
<script>
const context = new AudioContext();
let oscillator;
// Loads module script via AudioWorklet.
const start = () => {
context.audioWorklet.addModule('gain-processor.js').then(() => {
oscillator = new OscillatorNode(context);
let gainWorkletNode = new AudioWorkletNode(context, 'gain-processor');
oscillator.connect(gainWorkletNode).connect(context.destination);
oscillator.start();
});
}
const end = () => {
oscillator.stop();
}
document.getElementById("startButton").addEventListener("click", start);
document.getElementById("stopButton").addEventListener("click", end);
</script>
</body>
js
class GainProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{ name: 'gain', defaultValue: 1 }];
}
constructor() {
super();
}
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const gain = parameters.gain;
for (let channel = 0; channel < input.length; ++channel) {
const inputChannel = input[channel];
const outputChannel = output[channel];
if (gain.length === 1) {
for (let i = 0; i < inputChannel.length; ++i)
outputChannel[i] = inputChannel[i] * gain[0];
} else {
for (let i = 0; i < inputChannel.length; ++i)
outputChannel[i] = inputChannel[i] * gain[i];
}
}
return true;
}
}
registerProcessor('gain-processor', GainProcessor);
注意:例子生成的是音频,所以无用例截图。
Web Audio API - AudioWorklet的接口用于提供在单独线程中执行的自定义音频处理脚本,以提供非常低延迟的音频处理。
Web Audio API 的 AudioWorkletNode 接口代表用户定义的 AudioNode(在 WebRTC 中 AudioNode 表示一个音频处理模块) 的基类,它可以与其他节点一起连接到音频路由图。 它有一个关联的 AudioWorkletProcessor,它在 Web 音频渲染线程中进行实际的音频处理。
3.4 加上存储能力的 Web Workers,Service Worker
可以理解 Service worker 是一个在页面和网络之间的拦截器,用于缓存和拦截请求;
Service Worker 是在 Web Worker 的基础之上加上储存功能。Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
html
<body>
<button id="l-btn">发送消息</button>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('./serviceWorker.js', { scope: './' })
.then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function (err) {
console.log('ServiceWorker registration failed: ', err);
});
});
// 监听消息
navigator.serviceWorker.addEventListener('message', function (e) {
const data = e.data;
console.log('我接受到消息了:', data);
});
document.getElementById('l-btn').onclick = function () {
navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage('测试,传送消息,我发送消息啦。。。');
};
}
</script>
</body>
js
this.addEventListener('install', function (event) {
event.waitUntil(
caches.open('v1').then(function (cache) {
return cache.addAll(['./index.html']);
})
);
});
/* 注册fetch事件,拦截全站的请求 */
this.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
);
});
/* 监听消息,通知其他 Tab 页面 */
this.addEventListener('message', function(event) {
this.clients.matchAll().then(function(clients) {
clients.forEach(function(client) {
if(!client.focused) {
client.postMessage(event.data)
}
})
})
})
Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。所以本质上来说 Service Worker 并不自动具备"广播通信"的功能,需要改造 Service Worker 添加些代码,将其改造成消息中转站。在 Service Worker 中监听了message事件,获取页面发送的信息。然后通过 self.clients.matchAll() 获取当前注册了 Service Worker 的所有页面,通过调用每个的 postMessage 方法,向页面发送消息。这样就把从一处(某个Tab页面)收到的消息通知给了其他页面。
3.5 小结
Web Workers,Service Worker 和 Worklet。这些其实本质上来说都是 " Javascript Workers",尽管它们在 worker 方式上确实有一些相似之处,但是它们在用途上几乎没有重叠。
广义上讲,Web Workers 是在与浏览器页面线程分开的线程上运行的脚本。通过 <script>
标记的 HTML 文档中包含的是典型 Javascript,该文件将在页面线程上运行。Service Worker 简单来说是一种专门用于浏览器与网络和/或缓存之间的代理。而 Worklet 是一个非常轻量级的,高度特定的 worker。它们使我们作为开发人员能够参与浏览器渲染过程的各个部分。
4. 实战性能优化
4.1 保持 UI 响应
未使用 Web Workers:
使用 Web Workers:
4.2 Canvas 离屏渲染
由于 Web Workers 中不能进行 DOM 操作,所以在 Worker 中没有Canvas API。而 OffscreenCanvas 并不依赖DOM,所以在 Worker 中 Canvas API 可以被 OffscreenCanvas 来代替。任务繁忙的主线程也不会影响在Worker上运行的动画。所以即使主线程非常繁忙,你也可以通过此功能来避免掉帧并保证流畅的动画。
主线程 Canvas 渲染:
Worker 线程 Canvas 渲染:
4.3 大图离屏渲染
一些超大分辨率的图片,在渲染时会极大的影响渲染,造成用户交互卡顿。在这种情况下对于一些不可控的超大图片,使用了一 个前端压缩的方案,利用 WebWorker、 OffscreenCanvas 压缩图片,保证用户的交 互不卡顿。
4.3.1 整体架构设计
4.3.2 Woker 线程中整体流程
4.3.3 核心代码
js
// 核心代码
const res = await fetch(src);
const srcBlob = await res.blob();
const imageBitmap = await createImageBitmap(srcBlob);
const size = offScreen.width;
offScreenCtx.clearRect(0, 0, size, size);
offScreenCtx.drawImage(imageBitmap, 0, 0, size, size);
const blobUrl = await offScreen.convertToBlob().then(blob => URL.createObjectURL(blob));
ctx.postMessage({
type: 'success',
payload: {
origin: src,
dist: blobUrl,
}
});
整个流程有几个比较关键的点:
使用 Fetch 获取资源:
- 封装了 Promise 机制,优化了异步问题;
- 采用模块化设计,比如 rep、res 等对象分散开来,比较友好;
- 通过数据流对象处理数据,可以提高网站性能;
使用二进制 Blob 对象:
Blob(Binary Large Object)表示二进制类型的大对象。Blob对象表示一个 不可变、原始数据的类文件对象。它的数据可以按文本或二 进制的格式进行读取,也可以转换成ReadableStream来用于数据操作。
使用 createImageBitmap 获取****矩形的位图数据 ImageBitmap:
createImageBitmap 返回一个 ImageBitmap 的 Promise,当 Promise 成功时 resolves 接收一个包含所得到的矩形的位图数据 ImageBitmap。并且 ImageBitmap 也有比较明显的优势。
更好的性能:原因是无论是绘制在 canvas 2d 上,还是在 WebGL 创建纹理、贴图,当浏览器使用图像时都需进行一次解码 (decode) 处理,把 PNG、JPEG 等原始格式的图像转换为位图,即每个像素点都用 RGB 或 RGBA 来表示,当图片过大时,解码会有一定的消耗,并且这个过程是同步的。使用 ImageBitmap 性能更加好一点;
4.3.4 效果对比
优化前: 优化后:
4.4 高性能表格
高性能表格这一块,同一个场景模拟来进行性能的对比。
场景主要模拟大数量级别(10000 条数据以上)的表格渲染,模拟场景分为两部分:
- Client 功能:筛选功能 (按 ID 排序、乱序),无限滚动表格;
- Server 功能:数据我们提前扒下来保存成 JSON,Server 提供一个 HTTP API,不传 HTTP 参数获取全部 数据,同时可传入分页数据,按页获取数据
4.4.1 常规表格
这里就不放性能测试报告了,因为实在是太卡了,太卡了,太卡了,重要的事情说三遍。当然如果有兴趣,可去行测试。
4.4.2 虚拟滚动表格
对于虚拟列表来说,即使 1000W 条数据依旧很快,并且没有数据拷贝的损耗,数据排序时间也很快,但是在进行操作时会存在的明显 UI 阻塞。
4.4.3 Web Workers 表格
整体上来看,对比虚拟列表,Web Workers 表格从数据获取到渲染时间更慢,排序之后数据操作的时间成本更高,但是 Web Workers 表格优势在于操作时无 UI 阻塞。
4.4.4 Web Assembly 表格
横向对比,发现从数据获取到渲染时间 Web Assembly 表格比非 Worker 的更慢。和 Web Workers 表格一样,Web Assembly 表格也无 UI 阻塞。但是 Web Assembly 的在当前场景的主要优势是计算。
可以说,Webassembly 是 JS 之后浏览器支持的新语言。之前浏览器只能运行 JS 语言,现在多了一个,我们可以直接加载并执行 wasm。wasm 不同于引擎执行的 js,引擎执行的 js 是文本格式,而 wasm 是二进制。
wasm 类似一个微虚拟处理器,它可以将任何高级语言转换成可以在所有主流浏览器上运行的机器码,与 JavaScript 相比,wasm 简化了整个编译过程。
4.4.5 小结
上面三种表格优化方案,其实你会发现他们都有自己的优点,也都有缺点。那是不是我们将他们三者结合起来了?答案是肯定的。
- 使用虚拟列表来减少 DOM 渲染;
- Web Worker 可以生成独立的线程完成 CPU 密集型的操作,从而不阻塞主线程渲染;
- 利用计算性能更优的语 言,将计算部分用 WebAssembly 里来实现加速;
4.5 石墨文档 Web Workers 优化
石墨文档使用 Web Workers 对一个 2500 行的石墨表格进行排序优化。
未使用 Web Workers:
测试不使用 Web Worker 的表格 使用 Web Workers:
石墨文档中优化的主要操作是将大部分 Scripting 的工作移到了 Web Workers 中,Scripting 的时间从 9984ms 减少到 3650ms,虽然大部分工作还是 Scripting,但是相比没有 Web Workers,用户的排序操作的响应速度明显快了很多。
4.6 PROXX 游戏的优化
测试页面戳这里
为了确保这款游戏在功能机上灵敏流畅运行,这个小游戏使用了一种类 Actor 的架构。主线程负责渲染 DOM 和 捕捉 UI 事件。Worker 线程负责状态处理和游戏逻辑处理,它会确认你是否踩到雷上了,如果没有踩上,在游戏界面上应该如何显示。游戏逻辑甚至会发送中间结果到 UI 线程 来持续为用户提供视觉更新。
5. Web Workers 还可以做什么?
5.1 Angular2.0 & Web Worker
AngularJS 为了提高渲染性能,借助 Web Workers 将繁重的计算工作(例如 dirty checking)移到了 worker 中,而不阻塞主线程。
5.2 React & Web Worker
由于 VirtualDOM,React 很快。使用差异算法,浏览器 DOM 节点只有在状态发生变化时才会被操作。该算法在计算上是昂贵的。使用 Web Workers 执行计算可以使 React 更快。
未使用Web Workers:
5.3 redux & Web Worker
将 Redux 中 reducer 计算状态的部分移到 Web Workers 中去计算。redux & Web Workers 的主要思路就是:
- 将消息发布到工作线程的调度程序;
- 监听来自 Web Workers 的消息然后更新状态的替代 reducer;
- 使用调度程序在 Web Worker 中注册和执行任务的机制,它返回一个承诺,该承诺将在任务完成时解决。
6. Web Workers Library
6.1 Workerize
Moves a module into a Web Worker, automatically reflecting exported functions as asynchronous proxies.Github
6.2 Comlink
Comlink makes WebWorkers enjoyable. Comlink is a tiny library (1.1kB), that removes the mental barrier of thinking about postMessage and hides the fact that you are working with workers. Github
7. 已有代码如何使用 Web Workers
在项目中使用 Web Workers ,你需要很多心眼,因为你当时写模块 A 时并没有任何限制的,模块里面可能随意使用的 window、document 对象,操作了 DOM,用了其他模块 B 的内存,使得很难剥离出 A 模块使其能单独运行在 Web Worker 里。
那如果你是一个狠人,就是想做这个事情,那我觉得可以从以下方面去考虑考虑。
代码的剥离的考虑
你需要将关数据计算的操作都单独剥离出来,封装成纯函数,同样的输入,就会得到相同的输出,不依赖于当前环境的任何变量,以便他能被正常迁移。
消息传递机制的考虑
因为 Workers 之间是通过互相调用 sendMessage,onMessage 来通信的,也就会有很多消息注册,以及回调函数,所以你需要设计一个消息传递机制,并且还需要考虑兼容性。
传递数据格式的考虑
Workers 之间传输数据的性能比较,Workers 之间传输数据的类型有字符串、对象,也可以交换二进制数据,不同数据格式在传递时存在差异。
传递数据方式的考虑
输数据的方式有两种:
- 一种是通过拷贝的方式,通过内部的克隆算法(Structure cloned algorithm),将主线程的数据拷贝一份,传给 worker,这样 worker 改变数据不会影响到主线程。
- 另一种是通过转移的方式(Transferrable Objects),不做任何拷贝,而是直接将数据值的引用转移给 worker,而主线程不会再持有该数据的引用,这样也防止出现多个线程同时修改数据的问题。
Transferrable Objects 主要是采用二进制的存储方式,如果使用拷贝的方式,传输一个很大的二进制数据,会造成性能问题。而使用这种转移的方式来发送二进制数据,可以极大的提高传输效率,因为它不存在任何拷贝,那是否需要将我们现在的字符串或对象转换成二进制,然后使用 Transferrable Objects 的方式进行传输呢?其实转换成二进制的成本就很大,除非你的数据本身就是二进制(比如视频、文件等)。如果你的数据是 JSON,就通过简单的 JSON 传输,不要将其转换成二进制。
当然这里只是部分需要考虑的地方,还有很多其他需要考虑的地方,我这里就不写了,因为你问我,我也不知道。还是接着往下看把.....
8. Web Workers 为什么不常见?
Web Workers API 已经存在好几年了,但我们还没有看到社区的大量采用。我认为有几点原因:
-
设置复杂性、异步消息传递以及在不同上下文中思考的需要(不能在工作线程之间共享函数/引用),很难概念化的在 MVC 应用程序中利用 Web Workers 的强大功能。
-
前端最核心的功能就是 UI 渲染,而 Web Workers 没有操作 DOM 的能力。退一步说,假如 Web Workers 支持 DOM 操作,UI 渲染用多线程必然产生资源竞争和死锁的问题,代码实现的复杂程度大幅提升。
-
Web Workers 主要的作用是处理计算密集型任务,这在目前的前端领域本身也不是一个普遍的需求。而且,由于线程创建和通信有开销,实际上也需要一定规模的任务才能产生正向收益。在一些细分领域,比如网盘、在线文档、图像视频处理等工具型产品相对使用比较多。
-
Web Workers 的启动(约 14ms)、建立也需要额外开销,Web Workers 内的 JS 运行速度也有损失,如果不是需要并行,多次重复调用,对常规场景性能提升比较局限。
参考
- www.youtube.com/watch?v=Kz_...
- www.ruanyifeng.com/blog/2018/0...
- nextjs.org/docs/gettin...
- blog.csdn.net/weixin_4223...
- juejin.cn/post/716898...
- juejin.cn/post/708151...
- developer.mozilla.org/en-US/docs/...
- developer.mozilla.org/en-US/docs/...
- developer.mozilla.org/en-US/docs/...
- github.com/w3c/css-hou...
- audio.angularair.com/e/62-ngair-...
- web-perf.github.io/react-worke...
- github.com/chikeichan/...
- web-perf.github.io/react-worke...
- web-perf.github.io/react-worke...
- zhuanlan.zhihu.com/p/393428948
- betheme.net/a/4117447.h...
- github.com/GoogleChrom...
- surma.dev/things/is-p...
- developer.chrome.com/blog/transf...
- nolanlawson.com/2016/02/29/...
- dev.sankuai.com/code/repo-d...
- dev.sankuai.com/code/repo-d...