前面的章节都是告诉你怎么使用Worker,并没有真正的深入Worker的原理,这一章我们就来详细的了解一下Worker的原理。
Worker 的全局作用域
WorkerGlobalScope是Worker的全局作用域,它继承自EventTarget;
EventTarget
EventTarget是一个接口,它定义了一些方法,用来注册和触发事件,它的实现有Window、WorkerGlobalScope、Node、XMLHttpRequest等。
它拥有三个方法:
addEventListener:注册事件removeEventListener:移除事件dispatchEvent:触发事件
这些方法应该都很熟悉,我们在平时的开发中也经常使用,例如:
function onMessage (event) {console.log(event.data)
}
window.addEventListener('message', onMessage);
window.dispatchEvent(new MessageEvent('message', { data: 'hello' }));
window.removeEventListener('message', onMessage);
上面就是一个简单的事件注册、触发、移除的例子,这些都是通过EventTarget来实现的。
WorkerGlobalScope
上面提到了WorkerGlobalScope是Worker的全局作用域,它继承自EventTarget,所以它也拥有addEventListener、removeEventListener、dispatchEvent这三个方法。
它还有一些其他的属性和方法:
标准属性和方法
标准属性指的是规范中已经确定的属性,通常情况下它们都是固定不会再发生太大的变化:
self(只读):指向当前的WorkerGlobalScope,它的值和this是一样的;location(只读):指向当前WorkerGlobalScope的URL,它是一个Location对象;navigator(只读):指向当前WorkerGlobalScope的Navigator对象;importScripts():用来加载脚本,它的参数是一个或多个脚本的URL,例如:importScripts('a.js', 'b.js'),它的返回值是undefined,如果加载失败,会抛出一个NetworkError的异常;onerror:用来注册error事件的回调函数;onlanguagechange:用来注册languagechange事件的回调函数;onoffline:用来注册offline事件的回调函数;ononline:用来注册online事件的回调函数;onrejectionhandled:用来注册rejectionhandled事件的回调函数,它是Promise的一个事件;onunhandledrejection:用来注册unhandledrejection事件的回调函数, 它是Promise的一个事件;
在之前的示例中,我们用过self、importScripts、onerror,这些都是WorkerGlobalScope的标准属性。
参考:the-workerglobalscope-common-interface
非标准属性和方法
非标准属性指的是规范中还没有确定的属性,它们的实现是不稳定的,可能会在未来的版本中发生变化,所以不建议在生产环境中使用。
performance:指向当前WorkerGlobalScope的Performance对象,它是一个常规的Performance对象;console:指向当前WorkerGlobalScope的Console对象,它是一个常规的Console对象;dump():用来打印日志,几乎没有浏览器实现了这个方法,所以不建议使用,直接使用console.log就可以了;
上面这些属性和方法这里就先简单的了解一下,因为每一个属性都够一个单独的文章来讲解,所以这里就暂时不展开了。
self
self是一个只读属性,它指向当前的WorkerGlobalScope,它的值和this是一样的。
上面所有提到的属性都可以使用self来访问,例如:self.location、self.navigator、self.console等等。
同时也可以省略self,直接使用属性名来访问,例如:location、navigator、console等等。
这就像window对象一样,它的属性和方法都可以使用window来访问,也可以省略window,直接使用属性名来访问,例如:
// 通过 window 访问 location 属性
window.location.href
// 直接使用 location 属性
location.href
// 也可以使用 this 访问 location 属性
this.location.href
在WorkerGlobalScope中,self和this是一样的,所以可以省略self,直接使用属性名来访问,例如:
// 通过 self 访问 location 属性
self.location.href
// 直接使用 location 属性
location.href
// 也可以使用 this 访问 location 属性
this.location.href
其他属性的访问方式也是一样的,例如:navigator、console等等。
所以我们在写worker.js的时候,可以省略self,直接使用属性名来访问,例如:
addEventListener('message', function (event) {console.log(event.data)
})
上面的console其实也是一个WorkerGlobalScope的属性,它指向当前WorkerGlobalScope的Console对象。
这些都和我们使用window对象是一样的,所以我们可以把WorkerGlobalScope当成一个window对象来使用。
可以使用什么
上面着重的讲解了一下self属性,再加上最开始介绍了一下WorkerGlobalScope的属性和方法,所以我们可以把WorkerGlobalScope当成一个window对象来使用。
但是WorkerGlobalScope和window对象还是有一些区别的,我们来看一下下面这个例子:
// worker.js
addEventListener('message', function (e) {console.log(e.data)console.log(location.href)
})
省略
main.js的代码,很简单,这里只探索worker.js的代码。
虽然这里也可以使用location属性,但是它的指向并不是window.location,而是WorkerLocation.location。
它们之间的区别也很明显,window.location指向的是当前页面的地址,而WorkerLocation.location指向的是worker.js文件的地址。
不过这个并不是我们这次要谈论的主要问题,只是告诉大家,虽然在WorkerGlobalScope能用到很多和window对象一样的属性和方法,但是它们的结果,或者说指向的对象是不一样的。
在Worker中可以使用Web API有很多,但是它并不是挂载子啊WorkerGlobalScope,而是通过类似混入的方式,进入到Worker的上下文中,有如下:
- Broadcast Channel API
- Cache API
- Channel Messaging API
- Console API
- Crypto
- CustomEvent
- Data Store(仅 Firefox)
- DOMRequest 和 DOMCursor
- Fetch
- FileReader
- FileReaderSync(仅在 worker 中可用)
- FormData
- ImageData
- IndexedDB
- Network Information API
- Notifications
- Performance
- PerformanceEntry
- PerformanceMeasure (en-US)
- PerformanceMark (en-US)
- PerformanceObserver
- PerformanceResourceTiming
- Promise
- Server-sent 事件
- ServiceWorkerRegistration
- TextEncoder 和 TextDecoder
- URL
- WebGL 中的 OffscreenCanvas(通过特性首选项 gfx.offscreencanvas.enabled 启用)
- WebSocket
- XMLHttpRequest(尽管 responseXML 和 channel 属性始终为 null)
上面的列表来自MDN Worker 中可用的 Web API
除了上面提到的这些Web API,还有一些WorkerGlobalScope的属性和方法,就是文章最开始提到的,其他的就都不能使用了,例如document、window、parent等等。
DedicatedWorkerGlobalScope
上面讲到WorkerGlobalScope其实也就是一个接口,它是DedicatedWorkerGlobalScope的父接口;
DedicatedWorkerGlobalScope就是Web Worker的父类,这个就是我们今天的主角。
属性和方法
直接看函数签名:
interface DedicatedWorkerGlobalScope extends WorkerGlobalScope {readonly name: DOMString;postMessage(message: any, transfer?: Transferable[]): void;postMessage(message: any, options?: Transferable): void;close(): void;onmessage: EventHandler;onmessageerror: EventHandler;
}
在这里就出现了我们最开始使用的postMessage、onmessage和onmessageerror,这三个属性和方法是DedicatedWorkerGlobalScope的属性和方法。
看到函数签名之后,我们发现postMessage方法有两种重载,第二个参数是可选的,目前并没有找到它的具体用法;
通过HTML Standard的描述,这个参数是用来传输数据的,具体怎么用还没找到相关的资料,这个暂时先不讨论了。
而Worker就是实现了这个接口,所以我们可以在Worker中使用这些属性和方法。
Worker是我之前讲的三个Web Worker最简单的一个,看这个函数签名也就明白了,并没有太多的东西。
数据传输
Worker和window之间的数据传输,是通过postMessage和onmessage来实现的。
在我最开始的文章就讲过,postMessage只能传递JSON格式的数据,而且传递的数据是clone的,所以在Worker中修改传递过来的数据,不会影响到window中的数据。
但是这个clone的过程是有一定的限制的,并不是单纯的使用JSON.stringify和JSON.parse来实现的,而是使用结构化克隆算法来实现的,可以参考structuredClone
上面这个
API是浏览器的一个新特性,并不是Web Worker的特性。
以JSON.stringify来举例,在转换的数据中如果包含function、undefined、symbol等类型的数据,会忽略这些数据类型的数据,将符合转换的数据转换成JSON格式的。
但是在Worker中,这些类型的数据是不能被clone的,所以会直接报错,错误信息会通过onmessageerror来传递。
const worker = new Worker('worker.js');
const obj = {a: 1,b: undefined,c: function () {},d: Symbol('d'),
};
console.log(JSON.stringify(obj)); // {"a":1}
worker.postMessage(obj); // Uncaught DOMException: Failed to execute 'postMessage' on 'Worker'
上面的示例中,使用JSON.stringify来转换数据,可以看到b、c和d这三个属性都没有被转换,但是在Worker中,这三个属性都会报错。
const worker = new Worker('worker.js');
var obj1 = {name: 'obj1'
}
var obj2 = {obj1: obj1,name: 'obj2'
}
obj1.obj2 = obj2;
worker.postMessage(obj2);
console.log(JSON.stringify(obj2));
循环依赖使用JSON.stringify会报错,但是在Worker中,这种情况是可以被clone的。
了解这些我们就可以更自如的使用Worker了,你可以把Worker还有主线程都当做一个独立的服务器,它们之间的数据传输,就是通过postMessage和onmessage来实现的。
然后传递过去的数据,就是通过结构化克隆算法来实现的,只能传递JSON格式的数据,数据接收方可以随意操作这些数据,不会影响到数据发送方。
是不是很像服务器发送数据到前端,然后前端随便操作这些数据,不会影响到服务器。
总结
通过WorkerGlobalScope这个对象,我们可以看到Worker和window之间的区别,Worker中没有DOM、BOM、window、document等对象,但是它们都有navigator、location、XMLHttpRequest等对象。
同时也可以使用很多Web API,比如setTimeout、setInterval、fetch、postMessage等。
再通过WorkerGlobalScope认识到了DedicatedWorkerGlobalScope,它是Worker的一个实例,它的原型链上有WorkerGlobalScope,所以Worker中可以使用WorkerGlobalScope中的所有属性和方法。
然后通过WorkerGlobalScope的onmessage和postMessage,我们知道了Worker和主线程之间的数据传输,它们之间的数据传输,就是通过postMessage和onmessage来实现的。
最后onmessage和postMessage的数据传输,是通过结构化克隆算法来实现的,只能传递JSON格式的数据,数据接收方可以随意操作这些数据,不会影响到数据发送方。
虽然说Worker是一个独立的线程,但是通过这上面一系列的操作,Web Worker就没有线程安全的问题,就不像其他多线程语言一样,需要加锁来保证线程安全。
最后
整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:




文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取
本文深入探讨Web Worker的工作原理,解析其全局作用域WorkerGlobalScope及其继承的EventTarget接口。介绍了DedicatedWorkerGlobalScope的属性和方法,并详细解释了主线程与Worker之间的数据传输机制。
2264

被折叠的 条评论
为什么被折叠?



