前面的章节都是告诉你怎么使用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
就没有线程安全的问题,就不像其他多线程语言一样,需要加锁来保证线程安全。
历史章节和预告
- 🎉🎉🎉 Web Workers 使用秘籍,祝您早日通关前端多线程!
- 🥳🥳🥳Worker中还可以创建多个Worker,打开多线程编程的大门
- ✨✨✨ ServiceWorker 让你的网页拥抱服务端的能力
- 🎊🎊🎊深入 ServiceWorker,消息推送,后台同步,一网打尽!
- 🚂🚂🚂 ServiceWorker -> PWA的基石,在线离线都能玩!
- 💞💞💞SharedWorker 让你多个页面相互通信
- 当前章节
- SharedWorkerGlobalScope 居然只是看起来复杂
- ServiceWorkerGlobalScope 让你重新认识 ServiceWorker
- 构思中...