浏览器运行原理

浏览器多进程、js单线程,全面梳理js运行机制

浏览器是多线程的

首先抛出几个关键点-----浏览器是多线程的(可以在浏览器的任务管理器中验证),js是单线程(众所周知?)
在这里插入图片描述
可以看出,浏览器有一个主进程,并且每开一个tab,就会新开一个进程。浏览器多进程的原因做点儿解释:
相比于单进程浏览器,多进程有如下优点

  • 避免单个page crash影响整个浏览器
  • 避免第三方插件crash影响整个浏览器
  • 多进程充分利用多核优势方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

简单点理解:如果浏览器是单进程,那么某个Tab页崩溃了,就影响了整个浏览器,体验有多差;同理如果是单进程,插件崩溃了也会影响整个浏览器;而且多进程还有其它的诸多优势。。。

下面清点一下浏览器各个主要进程的工作(我的理解,把一个个进程当作一个个工厂,要产出一件合格的产品,需要各个工厂各司其职。)

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
  2. 负责浏览器界面显示,与用户交互。如前进,后退等
  3. 负责各个页面的管理,创建和销毁其他进程 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上 网络资源的管理,下载等
  4. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  5. GPU进程:最多一个,用于3D绘制等
  6. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为页面渲染,脚本执行,事件处理等

渲染进程

作为前端工程师,我们应该最为关心的是浏览器众多进程中的渲染进程
知识点:1,渲染进程是多线程的,那么下面梳理一下此进程中有哪些需要关注的线程呢
简单比喻:将一个线程视作一个工厂,这个多人协作(多线程)的工厂。那么这个工厂中有哪些工人呢?

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

  2. js引擎线程
    该线程也被称作JS内核,负责处理Javascript脚本程序。(例如V8引擎),该线程负责解析Javascript简本,运行代码。 JS引擎一直等待着任务队列的任务到来,然后加以处理,一个tab(rederer进程)中有无论什么时候都只有一个JS线程在运行JS程序。由于前面所说的GUI渲染线程和JS引擎线程是互斥的,所以如果JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞

  3. 事件触发线程
    归属于浏览器而不是JS引擎,(不才的理解,事件本身是归属于事件触发线程,只是事件的响应是JS引擎来处理的)用来控制事件的循环(原作者的话:可以理解为JS引擎自己都忙不过来,需要浏览器另开线程协助)当JS引擎执行代码块如setTimeout时(也可能时鼠标点击,或者ajax等),会将对应任务添加到时间线程中;当对应事件符合触发条件时,该线程会把事件处理添加到待处理队列的队尾,等待JS引擎的处理;注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去处理)

  4. 定时器触发线程
    传说中的setTime和setInterval所在的线程,浏览器定时计数器并不是由JS引擎计数的(这点想也想的明白,JS引擎是单线程的,而且计时器是异步的)
    因此通过单独线程来计时(计时完毕,添加事件到事件队列中,等待JS引擎空闲后执行)

  5. 异步HTTP请求线程
    在XMLHttpRequest连接后,通过浏览器新开一个线程请求,将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放到事件队列中,再由Javascript引擎执行
    巩固一哈:
    在这里插入图片描述

渲染流程

下面说说浏览器的渲染流程(不包括获取前端资源问题),大概可以分为以下几个步骤

1,解析html建立dom树
2,解析css构建render树(将css代码解析成树形数据结构,然后结合DOM合并成render树)
3,布局render树(layout+reflow),负责各元素的大小位置计算
4,绘制render树(paint),绘制页面像素信息

注释一下

  • layout:

有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。

  • painting:

按照算出来的规则,通过显卡,把内容画到屏幕上。

  • reflow(回流/重排):

当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

  • repaint(重绘):

改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

注意点

display:none 的节点不会被加入Render Tree,而visibility: hidden
则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。
display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。
有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

css加载是否会阻塞dom树渲染
这里说头部引入css

首先:css是由单独的下载线程异步下载的
重点来了: css加载并不会阻塞DOM解析过程但是会阻塞render树的渲染(因为render树需要用到css的信息)

事件循环进阶:macrotask与microtask

JS中分为两种任务类型:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task
重点来了(不同的异步也有顺序的(promise和setTimeout))

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');

它的正确执行顺序是这样子的:

script start
script end
promise1
promise2
setTimeout
那么问题来了;why?

这就是这一节的重点:
进一步,JS中分为两种任务类型:macrotaskmicrotask,在ECMAScript中,microtask称为jobs,macrotask可称为task

它们的定义?区别?简单点可以按如下理解:

  • macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
  • 每一个task会从头到尾将这个任务执行完毕,不会执行其它
  • 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task
    执开始前,对页面进行重新渲染
    task->渲染->task->...
  • microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
    也就是说,在当前task任务后,下一个task之前,在渲染之前
  • 所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
    也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

分别什么样的场景会形成macrotask和microtask呢?

  • macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)
  • microtask:Promise,process.nextTick等
    补充:在node环境下,process.nextTick的优先级高于Promise,也就是可以简单理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才会执行微任务中的Promise部分。

这里又有其他问题:Promise,process.nextTick到底谁先谁后呢?这就涉及其他知识点了

请上这里去看看

补充:在node环境下,process.nextTick的优先级高于Promise,也就是可以简单理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才会执行微任务中的Promise部分

再根据线程来理解下:

  • macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
  • microtask中的所有微任务都是添加到微任务队列(Job Queues)中,等待当前macrotask执行完毕后执行,而这个队列由JS引擎线程维护(这点由自己理解+推测得出,因为它是在主线程下无缝执行的)

所以,总结下运行机制:

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

在这里插入图片描述
另外,请注意下Promise的polyfill与官方版本的区别:

  • 官方版本中,是标准的microtask形式
  • polyfill,一般都是通过setTimeout模拟的,所以是macrotask形式
    请特别注意这两点区别
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值