工作线程(Web Worker)允许开发人员编写能够脱离主线程、长时间运行而不被用户所中断的后台程序,去执行 计算密集型 或 高延迟 的事务或者逻辑,并同时保证页面对用户的及时响应
Web Worker 分为 专用线程 Dedicated Worker 和 共享线程 Shared Worker
浏览器作为宿主环境提供了多线程运行 JS 的能力,可通过并行的方式高效地提升执行效率
共享 worker 可以在多个浏览器页面中访问同一个 Worker 实例,本期我们将基于共享 Worker 来设计符合业务场景的优化方案
在 脚本URL、Worker名称、文档源 origin 保持一致的情况下,多个页面共享同一个 SharedWorker。可以实现多页面共享数据,共享 webSocket 连接
主线程环境
-
创建一个共享Worker
const sharedWorker = new SharedWorker('prefetch.worker.js',{ name: 'prefetch_worker' })注:共享
Worker可以被多个上下文共同使用 -
消息的发送和接收
const worker = sharedWorker.port worker.postMessage() worker.onmessage = ({ data }) => { // event.data <=> data }
工作线程环境
-
消息的接收和发送
self.addEventListener('connect', ({ ports }) => { const client = ports[0] client.onmessage = ({ data }) => { // event.data <=> data } client.postMessage(data) })SharedWorker 是通过 port 来发送和接收消息的 -
加载外部脚本或第三方库
self.importScripts(urlA, urlB)注:所引入的脚本与库都会绑定在子线程的全局对象上,即
self或this上
注意事项
-
同源策略的限制
Worker线程中运行的脚本文件必须与主线程的脚本文件同源 -
操作 DOM 的限制
Worker线程中无法读取主线程所在视图的DOM对象,也无法使用window和document对象,但可以使用他们的子对象 -
本地文件的限制
Worker线程中无法读取本地文件,因此它所加载的脚本必须来自于网络 -
消息通信
Worker线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息的发送和接收来通信
场景示例
-
业务场景
有些固定参数的业务请求相对来说比较耗时,好几十秒都没有响应结果。导致页面呈现延迟,这样的用户体验极差
-
生活场景
公司大楼的电梯,每逢上班的时间点,都要等很长的时间,让人非常的烦躁。
如果说我们早一点,就可以直接坐上电梯了,避免了不必要地等待
共享线程数据预获取
-
工作线程(worker/worker.prefetch.js)
配置请求类型映射策略const strategies = Object.assign(Object.create(null), { 'GET': (url, options) => { const { params = {}, headers = {}, ...config } = options const queryString = Object.keys(params).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&') Object.assign(config, { url: queryString ? `${url}?${queryString}` : url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', ...headers } }) return config }, 'POST': (url, options) => { const { data = null, headers = {}, ...config } = options Object.assign(config, { url, body: data ? JSON.stringify(data) : null, headers: { 'Content-Type': 'application/json; charset=utf-8', ...headers } }) return config } })根据请求类型读取映射策略,发起请求获取数据self.addEventListener('connect', ({ ports }) => { const client = ports[0] client.onmessage = ({ data: [url, options] }) => { const { url: fetchUrl, ...adjunction } = strategies[options.method ? options.method.toUpperCase() : 'GET'](url, options) fetch(fetchUrl, { mode: 'cors', credentials: 'same-origin', ...adjunction}) .then(async response => { if (!response.ok) { throw new Error('Network response was not ok.') } else { const data = await response.json() client.postMessage(data) } }) } }) -
主线程封装(worker/share.index.js)
const wfetch = (url, options = {}) => { Object.assign(options, { 'headers': { 'Authorization': localStorage.getItem('AUTH_TOKEN') } }) const sharedWorker = new SharedWorker(`${location.origin}/worker/worker.prefetch.js`, { name: 'prefetch_worker' }) const worker = sharedWorker.port return new Promise(resolve => { worker.postMessage([url, options]) worker.onmessage = ({ data }) => resolve(data) }) } export default wfetch -
主线程处理
import wfetch from 'worker/share.index.js' wfetch(url).then(data => { console.log('Response Data:', data) }) -
基于 Webpack5 的进一步优化
const sharedWorker = new SharedWorker(new URL('./worker.prefetch.js', import.meta.url), { name: 'prefetch_worker' })通过以上语法可以实现不使用 bundler 就可以运行代码,也可以在浏览器中的原生 ECMAScript 模块中使用
浏览器调试
-
图示
-
图解
首先,通过
chrome://inspect打开浏览器新标签页然后,选择左侧
Shared workers选项查看执行中的线程列表最后,选择相应的执行线程,点击
inspect打开调试控制台
浏览器兼容性
-
在现代浏览器和移动端上的支持性并不是很理想
共享 Worker 在 Safari 浏览器中的支持比较糟糕,在项目中使用要做好优雅降级的方案
-
判断浏览器对共享
Worker的支持性if (window.hasOwnProperty('SharedWorker')) { console.log('支持,请放心使用!') } else { console.log('不支持,请优雅降级!') } -
一起交流学习
加群交流看沸点