从宏观层面理解——浏览器中JavaScript的运行机制

本文正在参加「金石计划」

众所周知,JS是单线程的,但是其在浏览器中的具体运行机制是怎样的呢?什么是微任务什么是宏任务呢?本篇文章我们从整体宏观层面进行梳理和分享。

浏览器都包含哪些进程

  • Browser进程
    • 浏览器的主进程(负责协调、主控),只有一个
    • 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • GPU进程
    • 最多一个,用于3D绘制等
    • GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。
  • 渲染进程
    • 浏览器内核,Renderer进程,内部是多线程的
    • 核心任务是将HTMLCSSJavaScript 转换为用户可以与之交互的页面,排版引擎Blink和Javascript引擎V8都是运行在该进程中,默认情况下,Chrome会为每个Tab标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
  • 网络进程
    • 主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 第三方插件进程
    • 每种类型的插件对应一个进程,仅当使用该插件时才创建
    • 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

注:进程间的通信是通过进程通信管道IPC来传递

从宏观层面理解——浏览器中JavaScript的运行机制

渲染进程包含了哪些线程

🙋‍♂️敲黑板:浏览器的渲染进程是多线程

它们分别是:

  • GUI渲染线程
  • JS引擎线程(JS引擎是单线程的
  • 事件触发线程
  • 定时触发器线程
  • 异步http请求线程

需要说明的是:

  • GUI渲染线程与JS引擎线程是互斥的(JS也是会改变布局和样式的,GUI渲染进程只会在JS执行之前或结束之后运行)
  • 异步http请求线程:在 XMLHttpRequest 连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。

Worker线程

  • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
  • JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)

浏览器渲染流程

  1. 解析html,建立dom树
  2. 解析css,构建style树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树
  3. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  4. 绘制render树(paint),绘制页面像素信息
  5. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。

渲染完毕后就是load事件了,之后就是自己的JS逻辑处理了

从宏观层面理解——浏览器中JavaScript的运行机制

load事件与DOMContentLoaded

  • DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片(譬如如果有async加载的脚本就不一定完成)
  • onload 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了(渲染完毕了)

所以,顺序是:DOMContentLoaded -> load

CSS加载

css是由单独的下载线程异步下载的。

  • css加载不会阻塞DOM树解析(异步加载时DOM照常构建)
  • 但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)

Event Loop

  • JS分为同步任务异步任务
  • 同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,事件触发线程管理着一个任务队列,栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
    • 如点击事件会放入事件队列中🤡,当点击事件触发的时候才会放入JS引擎执行栈后面等待执行;
    • AJax异步同理,在执行请求时,会在事件队列中添加事件,ajax请求完毕后,执行栈为空时就会读取事件队列中的事件,执行回调
  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行

延伸:setTimeout不能准时执行原因 => 因为主线程可能不为空闲(主线程运行时间不固定)

从宏观层面理解——浏览器中JavaScript的运行机制

定时器

由专门的定时器线程控制

当使用setTimeoutsetInterval时,它需要定时器线程计时,计时完成后就会将特定的事件推入事件队列中。

setTimeout模拟setInterval

最佳实践是:用setTimeout模拟setInterval,或者特殊场合直接用requestAnimationFrame

setInterval是每次都精确的隔一段时间推入一个事件,但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了

累计效应(上面提到的),如果setInterval代码在(setInterval)再次添加到队列之前还没有完成执行,就会导致定时器代码连续运行好几次,而之间没有间隔。

宏任务/微任务

macrotask(又称之为宏任务)

每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

  • 每一个task会从头到尾将这个任务执行完毕,不会执行其它
  • 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染

🙋‍♂️敲黑板啦:task->渲染->task->...

microtask(又称为微任务)

在当前 task 执行结束后立即执行的任务(在渲染之前)

🙋敲黑板啦:宏任务 -> 微任务 -> 渲染 -> 下一个任务...


运行机制总结

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

从宏观层面理解——浏览器中JavaScript的运行机制

资料

原文链接:https://juejin.cn/post/7223339726140260410 作者:小帅不太帅

(0)
上一篇 2023年4月19日 上午11:02
下一篇 2023年4月19日 上午11:12

相关推荐

发表回复

登录后才能评论