# 引言
首先我们知道 JS 中是没有并行,并发等概念的,这是因为 JS 是一门单线程的语言,其中的异步执行也是利用事件循环机制进行的,并不是严格意义上的异步。由于 JS 单线程的特性,使得 JS 在执行时如果遇到大量的任务就会使得过程运行缓慢。主渲染线程的延迟明显,使得用户体验较差.
# web-worker
web-worker 的作用就是用来缓解这一问题的
利用 web-worker, 我们可以利用浏览器新开辟一个进程,将需要大量执行的过程在这个进程中执行,然后将结果返回给我们的主进程即可.
# 使用示例
# worker.html
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Document</title> | |
<style> | |
.p{ | |
line-break: unset; | |
} | |
</style> | |
<a id="link"></a> | |
<p class="p"> | |
<%= sjdfl =%> | |
</p> | |
</head> | |
<body> | |
<script> | |
const \u4e2d\u6587 = '这是转义之后的字符'; | |
// 使用 webworker, 分担主线程的压力 | |
// const worker = new Worker() | |
// 创建一个 worker | |
const worker = new Worker('./worker.js') | |
console.log(worker); | |
worker.postMessage('sdfj'); | |
worker.postMessage({method:'echo',argus:[1,2,3,4]}) | |
// 主线程关闭 worker | |
setTimeout(()=>{ | |
worker.terminate() | |
// worker.postMessage('close') | |
},1000) | |
worker.addEventListener('message',(e)=>{ | |
console.log(e.data); | |
}) | |
/** | |
* 主线程监听 worker 错误 | |
*/ | |
worker.onerror=(e)=>{ | |
worker.terminate() | |
console.error('worker错误'); | |
} | |
/** | |
* 直接转移数据的控制权 | |
*/ | |
// 创建 4 字节的缓冲区 | |
let abfer = new ArrayBuffer(4); | |
// 创建 32 位整数数组作为视图,引用缓冲区 | |
let tes = new Int32Array(abfer); | |
console.log(tes,'ArrayBuffer'); | |
worker.postMessage(abfer,[abfer]); // 转交控制权,主线程不再存储 | |
/** | |
* 模拟文件下载功能 | |
*/ | |
const blob = new Blob(['文件下载']); | |
const link = document.getElementById('link'); | |
link.href = window.URL.createObjectURL(blob);; | |
link.download = '测试文件.txt'; | |
setTimeout(()=>{ | |
// link.click(); | |
},2000) | |
/** | |
* 创建 worker 线程加载 js 文件 | |
* !!必须指定 script 标签的 type 为浏览器不知道的类型 | |
*/ | |
// const blob1 = new Blob(document.getElementById('scriptDom').textContent); | |
// const url1 = new window.URL.createObjectURL(blob1); | |
// const worker2 = new Worker(url1); | |
/** | |
* 使用 worker 线程,完成轮询 | |
*/ | |
// 创建 worker | |
function createWorker(f){ | |
const fnBlob = new Blob(['('+f.toString()+')()']) | |
const fnUrl = window.URL.createObjectURL(fnBlob); | |
return new Worker(fnUrl); | |
} | |
const fnWorker = createWorker((e)=>{ | |
let cache; | |
// setInterval(()=>{ | |
fetch('/data').then(res=>{ | |
if(cache != res){ | |
console.log('变化了'); | |
} | |
}) | |
// },1000) | |
}); | |
</script> | |
<script id="scriptDom" type="app/worker"> | |
console.log('我是被woeker加载的文件'); | |
</script> | |
</body> | |
</html> |
# worker.js
// 接受数据 | |
console.log(self); | |
self.addEventListener('message',(e)=>{ | |
console.log(e.data); | |
self.postMessage('我接受到了你的消息') | |
if(e.data === 'close'){ | |
self.postMessage('听你的我关闭'); | |
self.close(); | |
} | |
},false) | |
// self.addEventListener('close',(e)=>{ | |
// console.log (' 关闭 '); | |
// }) | |
/** | |
* worker 内部加载脚本 | |
* 只能加载,不能获取其中内容 | |
*/ | |
importScripts('./main.js'); |
# serviceWorker
//serviceWorker 是基于事件驱动的。 | |
//serviceWorker 安装事件 | |
self.addEventListener('install',event=>{ | |
console.log(event,'安装事件'); | |
event.waitUntil(()=>{ | |
console.info('安装完成'); | |
}) | |
}) | |
//serviceWorker 激活事件(当安装完成之后就开始激活) | |
self.addEventListener('active',(event)=>{ | |
let cacheWhiteList = ['product-v2']; | |
event.waitUntil( | |
caches.keys().then(cacheNames=>{ | |
return Promise.all( | |
cacheNames.map(cacheName=>{ | |
if(cacheWhiteList.indexOf(cacheName) === -1){ | |
return caches.delete(cacheName); | |
} | |
}) | |
) | |
}) | |
) | |
}) | |
//serviceWorker 与网页的通信 | |
self.addEventListener('active',(event)=>{ | |
event.waitUntil( | |
self.clients.matchAll().then((client)=>{ | |
client.postMessage({ | |
msg:"Hello serviceWorker", | |
source:"serviceWorker" | |
}) | |
}) | |
) | |
}) |
# sharedWorker
//sharedWorker 示例 | |
// ============ 1. 创建 sharedWorker(shared-worker.js) ============ | |
// 这是一个独立的 worker 文件,需要在 HTTP 服务器环境下运行 | |
// 因为浏览器对 file:// 协议的 SharedWorker 有安全限制 | |
const ctx = self; // SharedWorker 的全局上下文 | |
ctx.onconnect = function (e) { | |
const port = e.ports[0]; | |
port.onmessage = function (event) { | |
const { type, payload, id } = event.data; | |
let response; | |
switch (type) { | |
case 'ping': | |
response = { message: 'pong', timestamp: Date.now() }; | |
break; | |
case 'broadcast': | |
// 广播消息给所有连接的端口 | |
ctx.clients.forEach((client) => { | |
client.postMessage({ type: 'broadcast', payload }); | |
}); | |
response = { success: true, clientCount: ctx.clients.length }; | |
break; | |
case 'getClients': | |
response = { clientCount: ctx.clients.length }; | |
break; | |
default: | |
response = { error: 'Unknown message type' }; | |
} | |
port.postMessage({ id, response }); | |
}; | |
}; | |
// ============ 2. 主线程 使用 sharedWorker ============ | |
//sharedWorker 和普通 worker 的核心区别: | |
// 1. 多个页面 / 标签页可以共享同一个 worker 实例 | |
// 2. worker 可以通过端口与每个页面通信 | |
// 3. worker 可以广播消息给所有连接的客户端 | |
// 创建 SharedWorker 实例 | |
const worker = new SharedWorker('./shared-worker.js', { name: 'my-shared-worker' }); | |
// 获取通信端口 | |
const port = worker.port; | |
// 生成唯一 ID 追踪响应 | |
let requestId = 0; | |
const pendingRequests = new Map(); | |
// 监听来自 worker 的消息 | |
port.onmessage = function (event) { | |
const { id, response } = event.data; | |
const resolver = pendingRequests.get(id); | |
if (resolver) { | |
resolver(response); | |
pendingRequests.delete(id); | |
} | |
}; | |
// 发送带回调的请求 | |
function sendMessage(type, payload) { | |
return new Promise((resolve) => { | |
const id = ++requestId; | |
pendingRequests.set(id, resolve); | |
port.postMessage({ id, type, payload }); | |
}); | |
} | |
// 启动端口 | |
port.start(); | |
// ============ 3. 示例用法 ============ | |
// 3.1 ping-pong 测试 | |
sendMessage('ping').then((res) => { | |
console.log('ping result:', res); | |
// { message: 'pong', timestamp: 1234567890 } | |
}); | |
// 3.2 在多个标签页间广播消息 | |
// 打开多个标签页,在一个标签页执行: | |
sendMessage('broadcast', 'Hello from Tab 1!').then((res) => { | |
console.log('broadcast result:', res); | |
// {success: true, clientCount: 3} <- 假设有 3 个标签页连接 | |
}); | |
// 3.3 获取当前连接的客户端数量 | |
sendMessage('getClients').then((res) => { | |
console.log('connected clients:', res.clientCount); | |
}); | |
// ============ 4. 关闭连接 ============ | |
// 页面关闭时 | |
window.addEventListener('beforeunload', () => { | |
port.close(); | |
console.log('SharedWorker connection closed'); | |
}); | |
// ============ 5. 注意事项 ============ | |
//- SharedWorker 需要通过 HTTP (S) 服务器访问(不能通过 file:// 协议) | |
//- 可以用 npx serve . 或 python -m http.server 启动本地服务器 | |
//- Safari 对 SharedWorker 的支持相对有限,测试时建议用 Chrome |