聊聊webworker那些事儿~

众所周知,javascript在最开始被设计时,只是为了最简单的页面交互而设计的,因此在秉持这个理念下,js被设计成 单线程 的模型,这样可以避免多线程模型下的临界资源竞争、死锁等问题

单线程模型的确够简单,也完全符合当时重浏览的网页,js也确实不需要强大的性能,但随着技术的发展,现代网页逐渐从 重浏览 变为了 重交互 ,这样就给js提出了更高性能的要求,于是乎当初的单线程模型如今变成了提升性能的绊脚石,人们急需一个"高性能"的js,在这样的需求下,针对提高js性能的各种方案应运而出,而本文的主角 webworker 也是其中一员,那么它具体是如何提高js性能的,就请看下面的正文吧~

浏览器架构

在聊webworker之前,我想先聊聊关于浏览器本身的东西,因为我觉得在了解了浏览器的架构之后,是可以更好地去理解webworker的

chrome 为例,它是采用的 多进程 的架构,具体由以下进程组成:

  • 浏览器进程 ,主要负责:
    1. 包括地址栏,书签栏,前进后退按钮等部分的工作
    2. 负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问
    3. 负责各个页面的管理,创建和销毁其他进程
  • 渲染器进程 ,主要负责一个网页的内容显示,脚本解析执行等,也称为 浏览器内核
  • 插件进程,主要负责一个网页内所使用到的插件的管理
  • 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 ,可以被 同源的 浏览器上下文所共享

要想使用它们也非常简单,示例如下:

javascript 复制代码
    const myWorker = new Worker("worker.js"); //专用worker
    const mySharedWorker = new SharedWorker("worker.js"); //共享worker

需要注意如下几点

  • worker脚本的url必须跟当前浏览器上下文同源
  • worker无法访问dom
  • worker的数据通信采用的是 结构化克隆 的方法,因此主线程与worker拿到的其实是彼此通信数据的拷贝

通信的方式主要是通过 postMessagemessage事件 来进行,而对于共享worker的话,需要注意的是要显示地使用 port 来进行通信,具体使用实例如下

专用worker:

javascript 复制代码
   //主线程发送消息给worker
   myWorker.postMessage('这是一条来自主线程发往worker的消息');
   //主线程接受到来自worker的消息
   myWorker.onmessage = (e) => {
     console.log(e.data,"这是一条来自worker发往主线程的消息");
   };
    
   //worker接收来自主线程的消息
   onmessage = e=>{
     console.log(e.data,"这是一条来自主线程发往worker的消息");
     postMessage("这是一条来自worker发往主线程的消息")
   }

共享worker:

javascript 复制代码
   //主线程发送消息给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带来的代码复杂度以及额外的内存占用都是需要考虑的,我们需要综合考虑自己项目的情况来决定是否使用它,要看它带来的收益是否大于带来的成本,俗话说得好:适合自己的才是最好的~

相关推荐
腾讯TNTWeb前端团队35 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试