聊聊webworker那些事儿~
众所周知,javascript在最开始被设计时,只是为了最简单的页面交互而设计的,因此在秉持这个理念下,js被设计成 单线程 的模型,这样可以避免多线程模型下的临界资源竞争、死锁等问题
单线程模型的确够简单,也完全符合当时重浏览的网页,js也确实不需要强大的性能,但随着技术的发展,现代网页逐渐从 重浏览 变为了 重交互,这样就给js提出了更高性能的要求,于是乎当初的单线程模型如今变成了提升性能的绊脚石,人们急需一个“高性能”的js,在这样的需求下,针对提高js性能的各种方案应运而出,而本文的主角 webworker 也是其中一员,那么它具体是如何提高js性能的,就请看下面的正文吧~
浏览器架构
在聊webworker之前,我想先聊聊关于浏览器本身的东西,因为我觉得在了解了浏览器的架构之后,是可以更好地去理解webworker的
以 chrome 为例,它是采用的 多进程 的架构,具体由以下进程组成:
- 浏览器进程,主要负责:
- 包括地址栏,书签栏,前进后退按钮等部分的工作
- 负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问
- 负责各个页面的管理,创建和销毁其他进程
- 渲染器进程,主要负责一个网页的内容显示,脚本解析执行等,也称为 浏览器内核
- 插件进程,主要负责一个网页内所使用到的插件的管理
- GPU进程,主要负责GPU的相关渲染工作
这里说明下,在浏览器里,浏览器进程、GPU进程、插件进程 是全局唯一的,意思就是被所有网页共享的,而 渲染进程 是被每一个tab所独享的,意思就是新打开一个tab,就会创建独立的 渲染进程
对于前端工程师来说,最关注的应该就是 渲染进程,它由多个线程组成:
- js引擎线程,不必多言
- 渲染线程,用于渲染网页内容
- 事件触发线程,用于管理事件轮询,适时地将事件回调放入任务队列里
- 定时器触发线程,用于管理settimeout和setinterval,定时器的计��是专门交由它来做的
- 异步请求触发线程,用于管理异步请求触发与回调
这里的 渲染线程 和 js引擎线程 是互斥的,因此如果js引擎里执行一些耗时操作,渲染线程是会被挂起并等待js引擎执行完成后,才会响应用户的交互进而更新界面,这就会造成页面卡顿、掉帧,影响用户体验
下面给出浏览器架构图
什么是webworker
Web Worker 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务,使主线程(通常是 UI 线程)的运行不会被阻塞/放慢。
上面一段话是摘自MDN对它的描述,这里也谈谈我对webworker的看法
因为js是 单线程模型,因此在遇到一些耗时的任务时,是一定会被“卡住”的,又从上文得知,js引擎被卡住,那么渲染线程是会被挂起的,这样用户的交互就得不到及时响应,从而使页面卡顿、掉帧,而webworker的解法是直接 提供多线程模型,将一些耗时的任务下放到webworker中去执行,从而可以解放js主线程,最终避免渲染线程被挂起的尴尬局面
webworker具体分为两类
- 专用worker,被当前浏览器上下文所独占
- 共享worker,可以被 同源的 浏览器上下文所共享
要想使用它们也非常简单,示例如下:
const myWorker = new Worker("worker.js"); //专用worker
const mySharedWorker = new SharedWorker("worker.js"); //共享worker
需要注意如下几点
- worker脚本的url必须跟当前浏览器上下文同源
- worker无法访问dom
- worker的数据通信采用的是 结构化克隆 的方法,因此主线程与worker拿到的其实是彼此通信数据的拷贝
通信的方式主要是通过 postMessage 和 message事件 来进行,而对于共享worker的话,需要注意的是要显示地使用 port 来进行通信,具体使用实例如下
专用worker:
//主线程发送消息给worker
myWorker.postMessage('这是一条来自主线程发往worker的消息');
//主线程接受到来自worker的消息
myWorker.onmessage = (e) => {
console.log(e.data,"这是一条来自worker发往主线程的消息");
};
//worker接收来自主线程的消息
onmessage = e=>{
console.log(e.data,"这是一条来自主线程发往worker的消息");
postMessage("这是一条来自worker发往主线程的消息")
}
共享worker:
//主线程发送消息给worker
mySharedWorker.port.postMessage('这是一条来自主线程发往worker的消息');
//主线程接受到来自worker的消息
mySharedWorker.port.onmessage = (e) => {
console.log(e.data,"这是一条来自worker发往主线程的消息");
};
//worker接收来自主线程的消息
onconnect = (e) => {
const port = e.ports[0];
port.onmessage = (e) => {
console.log(e.data,"这是一条来自主线程发往worker的消息");
port.postMessage(workerResult);
};
};
可以看到,共享worker里,主要是借助 port 来与主线程进行通信的,实现方式上比专用worker稍微复杂一些
利用webworker,我们可以使用多线程的能力,最大限度发挥多核cpu的能力来使我们的网页更加丝滑,那这么棒的东西兼容性如何呢?
可以看到,兼容性方面是非常好的,可以放心使用~
结语
webworker可以让我们有多线程的能力去优化网页性能,并且兼容性方面也是非常棒的,但凡事得看两面,webworker带来的代码复杂度以及额外的内存占用都是需要考虑的,我们需要综合考虑自己项目的情况来决定是否使用它,要看它带来的收益是否大于带来的成本,俗话说得好:适合自己的才是最好的~
都看到这里啦,如果本篇文章对你有帮助,希望能 点个赞👍 支持下啦,你们的支持才是我最大的动力!😘
转载自:https://juejin.cn/post/7300715626816798731