js-从浏览器的进程线程到事件循环机制

112 阅读6分钟

浏览器的进程与线程

进程是正在运行的程序,它包括代码,操作系统分配的资源,例如内存。 进程是操作系统分配资源的最小单位。 线程是cpu调度的最小单位。我可能一个进程内包含多个线程。线程共享同一进程的资源。拿QQ来举例子,我们的接收消息,就是一个单独的线程,为了防止主界面的阻塞。而对于下载这种需要独立管理资源,即使出错,也不会干扰我们的主程序。使用单独的进程。

我们的浏览器是多进程的。他主要有以下五种进程

  • 主进程
  1. 进行协调管理子进程,例如每打开一个tab他会创建一个渲染进程。
  2. 管理用户界面,例如地址栏,用户的收藏,前进返回。
  • 渲染进程
  1. 负责html文档的解析,js的执行以及页面的渲染,事件处理。每个tab页代表一个进程。防止不同tab页崩溃影响其他的tab页。
  2. 但是当浏览器的资源紧急,例如打开多个tab页面,他可能会将同一网址tab页面公用一个渲染进程,甚至不同网址tab共用一个进程。
  • GPU进程

    负责3D 绘制 视频解码等。当我们的渲染进程渲染好页面给出指令,最终是由3D进程进行绘制页面。

  • 插件进程

    负责管理我们浏览器的插件,例如 vue develtools,react devtools。防止插件崩溃影响页面。

  • 网络进程

  1. 负责发起发起请求,将请求发送到服务器。还负责cookie,缓存等网络协议。
  2. 负责接收处理响应。
  3. 处理网络的安全
  • 实用程序进程

    文件的下载,打印,截图这类系统操作。

进程间通信通过IPC来进行通信。例如:

渲染进程中包括以下六个线程

  1. js引擎线程 负责我们js脚本的执行。他是单线程结构。
  2. http线程 负责处理ajax请求。当请求完成触发回调,通知事件触发线程。例如将fetch代码到http逻辑格式的转换。
  3. 定时器线程 负责定时,到时间同时事件触发线程。
  4. 事件触发线程 负责控制事件,例如鼠标点击等。
  5. GUI线程 负责我们HTML文档的解析,进行页面的渲染工作。
  6. webworker js 单线程对cpu密集型任务显得乏力,例如在js脚本你使用for进行一亿次循环,就会堵塞页面。 相当于是浏览器的多线程机制。单独开出来一个线程。我们可以将耗时计算放在这里。但是他不能访问DOM。

事件循环机制

js是单线程的,我们不能让耗时性任务阻塞我们的主线程。我们将耗时性任务放入队列中,等待我们的主线程 完成之后再执行异步任务。可能异步任务又会产生新的异步任务,我们又将其放入任务队列中。如此维护事件执行顺序的机制叫做事件循环机制。

任务队列中的任务分为宏任务和微任务。

宏任务有

  • 1.定时器
  • 2.IO
  • 3.事件
  • 4.requestAnimationFrame
  • 5.script
  • 6.setImmidate(在IO操作的回调中比定时器快)

微任务有

  • promise.then函数中的回调
  • queenmicroTask()中的回调 //底层实现使用promise.resolve方法
  • process.nextTick(); // 达到递归深度会有一个警告机制。
  • mutationObserver();//H5新特性,监听DOM变化

注意:

浏览器的事件循环

首先VSync是我们的显示器发出的一个信号,告诉浏览器我们要开始渲染下一帧(显示器显示),可以接受下一帧的画面数据了。 浏览器渲染的目标是提前将渲染好的画面(GPU的纹理)准备好,当显示器发出VSyns信号,我们就可以提供这个画面给他,让显示器渲染。

  • 首先,我们的宏任务会去执行
  • 执行完宏任务,清空我们的微任务队列
  • 这时候会去检查一下我们离下一次的VSync信号是否还有足够的时间,以及是否时间过长(如果时间不够则跳过此次渲染,如果还有过长的时间则不会进行下面的渲染,而是继续事件循环)
  • 首先会去执行我们的scroll和resize事件,他们相当于是自带一个raf节流
  • 执行RAF
  • 进行页面的渲染工作,样式计算,重绘重排...
  • 检查离Vsync还有多久?还有时间的话执行我们的RIC。
  • 接收到VSync信号,将渲染好的画面提供给显示器,开启新的一帧

看完上述流程,我们该有些疑问了,我们可能到达了一定的时间才会进行一次页面的渲染工作,也就是可能宏任务执行了多轮,浏览器怎么确保我要执行多少宏任务呢?

我们的浏览器会去维护一个期望的渲染时间点,这个时间点的周期跟我们的vsync的周期一致,如果距离这个时间点还很远,我们的浏览器可能会推迟渲染,执行更多的任务。当我们的渲染流程结束,假如还有时间,就会去执行我们的RIC。

发现没,其实我们在瞬间往宏任务队列里塞入100000个短时性的js任务,实际上并不会导致我们的页面的卡死,因为我们的页面的渲染是由浏览器调度决定的,每次循环,浏览器都会查看有没有到我们的渲染时间点,执行渲染。但是可能会造成我们的点击,滑动事件的回调不执行无响应。 长耗时性任务(js执行,渲染)才是我们页面的fps下降的原因。当然,我们的短宏任务操控了页面的话,就会造成大量渲染卡死页面。

如此设计也是为了防止在我们渲染的同时修改元素的属性,造成页面数据的不一致性。也就是我们常常说的js渲染和主线程互斥。当然,有些情况也会进行页面的立即渲染,例如。

  • element.style.width=10px; const width = window.getElement.width 修改样式并立即获取
  • offestTop 触发重新渲染
  • scrolly 获取滚动位置

注意:

  • resize事件和scroll事件在渲染流程中触发,浏览器会自动合并成最后一次,在页面渲染前执行,相当于自带一个raf节流。

  • 不是每轮eventLoop都有页面更新。