浏览器渲染原理(一)

一开始我只想弄明白js在浏览器里面究竟是怎么执行的,发现本身须要补补基础,因而打算总结一个文章来补一下基础,有什么不对的地方还请大佬们指正。css

这篇文章大算分几个章节总结一下本身学到的知识点,顺便复习一些计算机基础知识,了解JS代码在浏览器是如何执行的,如何渲染页面的。html

进程和线程

-进程是一个工厂,工厂有它本身独立的资源,工厂之间相互独立;前端

-线程是工厂中的工人,多个工人协做完成工厂的任务;git

-工厂内能够有一个或者多个工人;github

-工人之间共享空间;浏览器

而后咱们再看下图,稍微再解释一下。markdown


首先计算机能够有不少应用程序,浏览器就是其中之一。浏览器能够有不少个模块(进程),每一个模块又独立且分配有本身的资源(线程)。网络

1.应用程序是有一个或多个模块组成的,以如今的谷歌浏览器举例,他有一个浏览器主进程、一个GPU进程、一个网络进程、多个渲染进程和多个插件进程数据结构

2.进程是由一个或者多个线程组成的,线程是进程的基本单位多线程

3.进程之间互相独立,有本身的资源,好比CPU的时间片,占用的内存。

4.一个进程下的线程(工人)共享进程(工厂)的资源,包括代码段数据段内存等

5.线程之间协做在进程中完成任务。

6.进程是CPU资源分配的最小单位,是能够拥有资源和独立运行的最小单位。

7.线程是CPU调度的最小单位,由于进程能够有一个或者多个线程。

浏览器是多进程的

浏览器是多进程的,有一个主控进程,以及每个tab页面都会新开一个进程(某些状况下多个tab会合并进程,例如多个空白页等特殊状况);以如今主流的谷歌浏览器来举例,它主要包括如下几个进程

1.浏览器主进程:浏览器主进程,负责协调主控,只有一个

  • 负责浏览器界面显示,于用户交换,前进后退等
  • 负责各个界面的管理,建立/销毁其它进程
  • 将Renderer进程获得的内存中的Bitmap,绘制到用户界面上
  • 网络资源管理,下载等

2.GPU进程:最多一个,用于3D绘制

3.插件进程:每使用一类插件都会建立一个进程

4.浏览器渲染进程(内核):默认每打开一个tab页,都会新建立一个。互不影响,控制页面渲染,脚本执行,事件处理等等。(有时候会优化,如多个空白页tab会合成一个)


tips:如图,打开任务管理器,打开三个tab页,就会建立三个进程。负责控制渲染本身的页面内容。那为何浏览器是多进程的?你下下,若是是单进程,某个tab页卡死或者崩溃了,就会影响到其它tab页

浏览器渲染进程(内核)

对于浏览器应用的这些进程,咱们只须要知道只有一个浏览器主进程,多个协做进程(GPU、渲染进程、网络进程、插件进程等等)。前端只须要重点关注渲染进程就能够了。由于渲染进程是渲染页面的。而后这个渲染进程,它是多线程的,若是还不明白进程和线程的关系,联想一下工厂和工厂的关系。回头继续看图理解一下进程和线程的关系。


GUI渲染线程

GUI渲染线程主要负责渲染页面的,解析HTML、CSS,构建成一个DOM树和render树;当界面须要重绘或者重排引起回流时,这个线程会执行。注意,GUI渲染线程和JS引擎执行是互斥的,当js引擎执行时,GUI线程会被挂起,保存到一个任务队列中,等到JS引擎线程空闲时候才会出队被当即执行。若是不互斥,你一边用户操做一遍渲染界面,会致使渲染不许确等,这就决定了JS是单线程的了。总结为如下几点:

  • 渲染界面,解析html、css、构建dom树和renderObject树,布局和绘制等。
  • 当界面须要重绘或者某种操做须要回流的时候,都会执行这个线程
  • GUI线程和JS引擎线程是互斥的

JS引擎线程

负责处理js脚本,解析脚本运行代码。可是js引擎会一直等待着任务队列的到来,而后加以处理。一样,GUI渲染线程和JS引擎执行是互斥的,若是js执行的时候太长,就会致使页面渲染不连贯,也就是阻塞页面,致使了页面渲染加载被阻塞了,这就是为何经常不把script标签放在头部而放在body底部下的缘由。总结为如下几点:

  • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
  • JS引擎线程负责解析运行Javascript脚本代码。
  • JS引擎会一直等待着任务队列中任务的到来,而后加以处理,一个Tab页(渲染进程)中不管何时都只有一个JS线程在运行JS程序
  • 同理,GUI线程和JS引擎是执行时互斥的

事件触发线程(Event lop)

这个事件触发线程是归属浏览器的,而不是JS引擎的,能够这么理解,由于JS引擎是单线程的,它很忙,必需要有协助线程协助它完成一些事件,而这些事情,就是事件循环。

当JS引擎执行到一些网络请求,setTimeOut等异步的代码块时,会将这些异步的任务加入到事件线程中。当这些任务符合处理条件的时候,该线程会把任务加到待处理队列的队尾。等待JS引擎执行。(JS引擎为空时候才执行这个队列的任务)总结为如下几点:

  • 该线程是归属浏览器的,不是JS引擎的,用来控制事件循环机制
  • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其余线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
  • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
  • 因为JS是单线程的,全部这些事件都得排队等待JS引擎执行。(引擎空闲时出队执行)

定时触发器线程

传说中的 setInterval setTimeout 所在线程,浏览器定时器不是由JS引擎计时的,你想呀,由于JS引擎是单线程的,若是执行这些计算器,阻塞页面彻底中止了,用户就不用点网页了。所以经过单独线程来计时并触发定时,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行。总结为如下几点:

  • setInterval 和 setTimeout所计时的线程
  • 计时完毕后,会加入事件队列中,等待JS引擎执行。(JS引擎空闲时执行)
  • W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

异步http请求线程

在 XMLHttpRequest 在链接后是经过浏览器新开一个线程请求,当检测到状态变动时,若是设置有回调函数,异步线程就产生状态变动事件,将这个回调再放入事件队列中。再由JS引擎执行。

  • 在XMLHttpRequest在链接后是经过浏览器新开一个线程请求
  • 将检测到状态变动时,若是设置有回调函数,异步线程就产生状态变动事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

tips:到这里,你应该知道了浏览器打开一个页面显示出来的全过程了,若是不清楚,须要再回头看看。上面总提到一个事件队列,它是基于哪一个线程的呢?答案是事件触发线程

浏览器主进程和其它进程的通讯

browser主进程是一开始说浏览器的主进程,是控制协调其它进程的主进程。在打开一个Tab页的时候,就会建立一个主线程。经过主线程协调其它线程完成页面渲染。这里会涉及一些并发并行的操做系统知道点,就不展开扩展了,同时简单回顾下以前的整个流程。


  • Browser主进程收到用户请求后,首先须要获取页面内容(譬如经过网络进程下载资源),随后将该任务经过RendererHost接口传递给资源Render进程
  • Renderer进程的Renderer接口收到消息,简单解释后,交给渲染线程,而后开始渲染

    • 渲染线程接收请求,加载网页并渲染网页,这其中可能须要Browser主进程获取资源和须要GPU进程来帮助渲染,这期间都是主进程调度协助进程完成的。
    • 固然可能会有JS线程操做DOM(这样可能会形成回流/重绘)
    • 最后Render进程将结果传递给Browser进程
  • Browser主进程接收到每一个协做进程处理好的结果后,将结果绘制出来
tips:这一块流程若是深挖的话,几十篇文章都写不完,前端了解整个流程的协做运做就能够了

梳理浏览器渲染线程之间的关系

渲染进程,即浏览器内核,才是咱们前端关注的重点,再次强调一下。而后咱们必须知道一下几点。

  1. GUI线程和JS引擎线程是互斥的
  2. 由于第1点,若是JS引擎执行时间好久,就会引起页面阻塞
  3. HTML5中支持了Web Worker,以解决页面阻塞,可是Web Worker是JS引擎子线程,受JS引擎控制,JS单线程依旧不会改变

浏览器渲染流程

这个过程也很复杂,能够参考我以前的计算机网络(前端版)的问题,过多的细节不在这里描述。这里只作简单的描述

1.浏览器输入URL,浏览器主线程接管,开一个下载线程,已进行http请求。(忽略DNS等等)

2.拿到响应内容,将内容经过RendererHost接口转交给Renderer渲染进程

3.浏览器开始渲染(可能会协调GPU等线程协做完成)

渲染进程(内核)拿到内容后,分如下几步开始渲染:

1.解析HTML创建dom树

2.解析CSS构建render树,其实就是讲css解析成树的数据结构,而后结合dom树造成render树。

3.布局render树(Layout/reflow),复杂计算元素的大小、位置等信息

4.开始绘制render树(paint),绘制页面像素信息

5.浏览器会将绘制信息发送给GPU,GPU会将合成(composite),显示在屏幕上。

步骤并不详细,只是列个大概,更详细的请参考别的渲染文章,这里不进行深究。

tips:这也是为何页面渲染至少会触发一次回流的缘由。

这里参考一张图来梳理一下上面的步骤:


注意细节

  1. DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片。有async的脚本也不必定完成。
  2. 当 onload 事件触发时,页面上全部的DOM,样式表,脚本,图片都已经加载完成了。页面渲染完毕。
  3. css加载不会阻塞DOM树解析(异步加载时DOM照常构建);但会阻塞render树渲染(渲染时需等css加载完毕,由于render树须要css信息)
  4. 渲染步骤中就提到了composite的概念,能够简单的理解为,浏览器渲染图层通常包括两大类,就是普通图层复合图层。
  5. 文档就能够当作一个默认的复合图层,其次absolute这样的脱离文档流也依然属于复合图层。硬件加速什么的这里就不展开描述了。

谈谈JS的运行机制(Event Loop)

到此时,页面已是完成了初次渲染。

event lop其实就渲染进程里面的事件触发线程(线程里维护的队列),若是不记得了往上翻图回忆一下。它其实就是和JS引擎线程协做完成任务的。(界面的一些交换)

先从新温习一下,浏览器渲染进程的三个线程:

  • JS引擎线程
  • 事件触发线程

  • 定时触发线程

而后再提JS引擎主执行栈几个概念:

1.JS分同步任务和异步任务

2.同步任务都在主线程上执行,造成一个主执行栈

3.主线程以外,事件触发线程管理着一个任务队列(就是上文屡次提到的任务队列),只要异步任务有了运行结果,就在任务队列之中入队一个事件

4.一旦主执行栈中的全部同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行,以此循环。


tips:看到这里,应该就能够理解为何有时候setTimeout推入的事件不能准时执行?由于可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码, 因此天然有偏差。

总结

浏览器原理仍是很复杂的,了解它的渲染原理,能够很好的帮助咱们优化性能。对于像Event lop事件循环机制、渲染原理等等具体的细节。能够参考大佬们的文章,我这里只作一个小扩展。

参考文章:

yuchengkai.cn/docs/fronte…

sanyuan0704.github.io/frontend_da…

相关文章
相关标签/搜索