番外:对浏览器的执行过程的理解 - 01 基本概念的确立

01 基本概念的确立

基本的查阅资料

一、浏览器是什么样的

(1) 从结构上看

  1. 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
  2. 浏览器引擎 - 在用户界面和呈现引擎之间传送指令。
  3. 呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
  4. 网络- 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
  5. 用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
  6. JavaScript 解释器。用于解析和执行 JavaScript 代码。
  7. 数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

浏览器结构图

以上,说的更清楚一点,就是从服务(软件模块组成)去描述,一个浏览器需要这么多的服务。值得注意的是,这里的呈现引擎。

(2) 从进程,线程上看

  1. 浏览器主进程(Browser进程)
    浏览器的主进程,负责协调、主控,只有一个(无论打开几个tab或几个弹窗),主要作用:
    负责浏览器界面显示,与用户交互,如前进,后退等;
    负责各个页面的管理,创建和销毁其他进程;
    将Renderer进程得到的内存中的Bitmap,绘制到用户界面上;
    网络资源的管理,下载等;
    PS:看到有人说还有网络进程专门负责网络资源的加载,但是我从哪里都没找到证据论证,反而更多的人认为这个功能应该归属于浏览器的主进程

  2. GPU进程
    用于3D绘制等,可禁用掉,只有一个。

  3. 第三方插件进程
    每种类型的插件对应一个进程,仅当使用该插件时才创建。

  4. 浏览器渲染进程
    浏览器渲染进程(Renderer进程),即通常所说的浏览器内核进程,主要作用:页面渲染、脚本执行、事件处理等。每一个标签页的打开都会创建一个Renderer进程,且互不影响。默认一个标签页一个Renderer进程,但是,有时候浏览器会将多个进程合并(暂时没查到合并的依据)。它和结构上的呈现进程相对应。 下面是介绍它的线程。

    • GUI渲染线程:

      • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等;
      • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行;
      • 注意:GUI渲染线程与JS引擎线程是互斥的(什么是互斥,待会会讲到为什么),当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
    • JS引擎线程:

      • JS引擎线程也称为JS内核线程,负责处理Javascript脚本程序,解析Javascript脚本,运行代码;
      • JS引擎一直等待着任务队列中任务的到来,然后加以处理,JS线程是当线程的 => 一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
      • 注意:GUI渲染线程与JS引擎线程的互斥关系,所以如果JS执行的时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
    • 事件触发线程:

      • 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS单线程自己都忙不过来,需要浏览器另开线程协助);
      • 当JS引擎执行代码块如setTimeOut时(也可是来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务队列添加到事件触发线程中(注意队列用词);
      • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎线程空闲的时候进行处理;
    • 定时器触发线程:

      • 即setInterval与setTimeout所在线程
      • 浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确性
      • 因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中
      • 注意: W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果是小于4ms,则默认为4ms
    • 异步http请求线程

      • XMLHttpRequest连接后通过浏览器新开一个线程请求
      • 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行

      总结:
      浏览器进程、线程图

(3) 解释一些让人疑惑的点

  1. 该节(1)中的图的箭头并不是包含关系,而是连接关系!这表明他们是这样通知其它组件工作交流,或者说的直白一点:当url一旦确立,要展示页面是,他们的组件是怎么样通知的。关于这个解释如何论证?看最右边的Data 存储服务,它是独立的!
  2. 呈现引擎所对应的线程就是GUI线程,对的你可能以为是渲染进程?那你可能猜错了!引用一段话
    呈现引擎采用了单线程。几乎所有操作(除了网络操作)都是在单线程中进行的。在 Firefox 和 Safari 中,该线程就是浏览器的主线程。而在 Chrome 浏览器中,该线程是标签进程的主线程。
    网络操作可由多个并行线程执行。并行连接数是有限的(通常为 2 至 6 个,以 Firefox 3 为例是 6 个)。

    PS:这也说明了资源在理想情况下是能够(有能力)并行下载的!(如果什么都不考虑情况下;防止杆精)

二、一些问题的解释

(1) JS线程是单线程

本身单线程是不存在这个概念,单线程只是相对多线程而存在。或者说JS不是多线程,关于为什么JS不是多线程:

  • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?(这段话摘自《阮一峰的网络日志:JavaScript 运行机制详解:再谈Event Loop》)总结:
    主要的一点,就是为了简单,没有DOM临界资源竞争,这也是为后面事件循环EventLoop的出现相互映衬

(2) 关于EventLoop

它其实在浏览器和NodeJs表现不一致。限于篇幅这里暂时不做过多解释。先挖个坑

(3) 浏览器渲染进程上面提到的各线程,并行?还是并发

  1. 假设它是并发,意味着,那些线程都是轮换的占用CPU:
    • 定时器线程不可能轮换的占用,无法保证计时
    • 现代的CPU大部分,几乎都是多核,为什么非要并发而不是并行,发挥多核的优势,因此并发没有必要
    • 并发,意味着效率低下,前面也说了,JS线程和CUI渲染线程互斥,如果是并发,那就没有互斥的必要啊! (互斥,是有临界资源产生竞争)
  2. 假设它是并行,意味着,那些线程都是使用不同核的CPU:
    • 定时器问题解决
    • CPU利用率高,发挥多核优势
    • JS引擎线程和GUI渲染线程互斥 的 前提 => 能并行

结论:浏览器渲染进程上面提到的各线程是并行的

(4) 为什么JS引擎线程和GUI渲染线程互斥

互斥,是有临界资源产生竞争。那这里临界资源是什么? DOM yes!
基于 (3),假设这两个线程不互斥,从一个时间点开始:

  • JS线程改变某个DOM节点的一个高度,GUI渲染线程 渲染 DOM蓝本
  • JS线程在 GUI渲染线程 渲染 DOM蓝本 完成之前,又删除 这个DOM节点,但是
    GUI渲染线程不能倒退,它已经到了要渲染的时间,页面必须出现变化
  • 于是,在JS线程的DOM蓝本是没有这个DOM节点,但是页面上依然显示,持续下去,整个页面和
    JS内存中的DOM蓝本,表现的不同,差异越来越大。

上面解释了,为什么GUI渲染线程要和JS线程竞争

(5) 究竟什么是主线程

一个进程至少有一个线程,即主线程,它是进程的子线程的入口。
如果按照这种情况看:浏览器的渲染进程可以分为以下线程

  • 主线程 Main thread,运行JavaScript、DOM、CSS、样式布局计算 (渲染线程GUI,JS线程)
  • 工作线程 Worker thread
  • N个工作线程:运行Web Worker,ServiceWorker,Worklet
  • 内部线程:Blink和V8会创建几个线程处理web audio,数据库,GC等
    • 排版线程 Compositor thread
    • 光栅线程 Raster thread
    • 定时器线程,等等,在进行线程介绍的时候有讲过

刚好JS线程和GUI线程本身就是互斥

综上:缩短JS线程的执行时间和提高执行效率对页面的展示有帮助。

(6) setTimeOut是什么时候将回调函数放入任务队列的?

答:在调用setTimeOut直到setTimeOut函数结束的那一段时间,如果简单一点理解(模糊),在JS线程的JavaScript引擎在解析setTimeOut函数时,调用底层接口通知定时器线程开始计时。定时器线程在计时完成,将回调函数入队任务队列,等待JS线程执行。

setTimeout(()=>{
    console.log('a')
}, 2000)
for(let i=0; i<5000000000; i++) {
    1-1 > 2
}
console.log('结束了')

这段代码说明setTimeout是在调用了,就开始计时,等到计时完成,直接插入任务队列,等待主线程执行该任务,但是主线程当前的JS代码for循环还未结束,因此等到for循环结束,打印“结束了”,再执行定时器任务前的任务,执行轮询到该定时器任务才打印"a"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值