
sunilwang
2022/11/07阅读:54主题:橙心
浅谈浏览器的渲染原理
前言
对于一个前端来说,每天打交道最多的就是浏览器。从刀耕火种时代的ie浏览器,到代表现代先进的chrome浏览器,浏览器的整个架构发生了翻天覆地的变化。了解浏览器,不仅可以帮助我们更好地理解前端工作,还会为我们在技术优化的层面上提供更多的角度。
了解进程与线程
在介绍浏览器之前,我们先了解进程与线程概念。 进程
是操作系统资源分配的基本单位,进程中包含线程。例如启动一个程序,就开启了一个进程。 线程
有时候进程不止要干一件事,要同时干多件,就需要同时运行多个子任务,把这些子任务称之为线程,为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型。
可以将进程和线程类比成工厂和工人,例如:
进程是一个工厂,工厂有它的独立资源 工厂之间相互独立 线程是工厂中的工人,多个工人协作完成任务 工厂内有一个或多个工人 工人之间共享空间
再完善两者之间的概念:
工厂的资源 -> 系统分配的内存(独立的一块内存) 工厂之间的相互独立 -> 进程之间相互独立 多个工人协作完成任务 -> 多个线程在进程中协作完成任务 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)
浏览器是多进程的
理解了进程与线程了区别后,接下来对浏览器进行一定程度上的认识:(先看下简化理解)
-
浏览器是多进程的 -
浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存) -
简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。

图中打开了Chrome浏览器的多个标签页,然后可以在Chrome的任务管理器中看到有多个进程(分别是每一个Tab页面有一个独立的进程,以及一个主进程)。 如果再多打开一个Tab页,进程正常会+1。
浏览器都包含哪些进程
知道了浏览器是多进程后,再来看看它到底包含哪些进程

-
Browser进程
:浏览器的主进程(负责协调、主控),只有一个。-
负责浏览器界面显示,与用户交互。如前进,后退等 -
负责各个页面的管理,创建和销毁其他进程 -
将Renderer进程得到的内存中的Bitmap,绘制到用户界面上 -
网络资源的管理,下载等
-
-
插件进程
:每种类型的插件对应一个进程,仅当使用该插件时才创建 -
GPU进程
:最多一个,用于3D绘制等 -
渲染进程
(浏览器内核):默认每个Tab页面一个进程,互不影响。主要作用是页面渲染,脚本执行,事件处理等 -
网络进程
:处理网络资源
浏览器多进程的优势
相比于单进程浏览器,多进程有如下优点:
-
由于默认 新开 一个 tab 页面 新建 一个进程,所以单个 tab 页面崩溃不会影响到整个浏览器。 -
第三方插件崩溃也不会影响到整个浏览器。 -
多进程可以充分利用CPU多核的优势。 -
方便使用沙盒模型隔离插件等进程,提高浏览器的稳定性。
缺点就是系统为浏览器新开的进程分配内存、CPU 等资源,所以内存和 CPU 的资源消耗也会更大,相当于用空间换时间了。
重点是浏览器内核(渲染进程)
上面提到了这么多的进程,那么,对于前端来说,最终要的是什么呢?答案是渲染进程
GUI渲染线程
-
负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。 -
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行 -
注意, GUI渲染线程与JS引擎线程是互斥的
,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
JS引擎线程
-
也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎) -
JS引擎线程负责解析Javascript脚本,运行代码。 -
JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序 -
同样注意, GUI渲染线程与JS引擎线程是互斥的
,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
事件触发线程
-
归属于浏览器而不是JS引擎
,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助) -
当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中 -
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理 -
注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
定时触发器线程
-
传说中的setInterval与setTimeout所在线程 -
浏览器定时计数器并不是由JavaScript引擎计数的
,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确) -
因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行) -
W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
异步http请求线程
-
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求 -
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JavaScript引擎执行。
GUI渲染线程与JS引擎线程是互斥的 由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。 因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起, GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。
浏览器渲染流程
在介绍浏览器渲染过程之前,先介绍下页面的加载过程,有助于更好理解后续渲染过程。
1.浏览器根据 DNS 服务器得到域名的 IP 地址 2.向这个 IP 的机器发送 HTTP 请求(http请求阶段) 3.服务器收到、处理并返回 HTTP 请求(http响应阶段) 4.浏览器得到返回内容(浏览器渲染阶段)
浏览器器内核拿到内容后,渲染大概可以划分成以下几个步骤:
-
构建dom树
-
浏览器无法直接使用 HTML,要先解析HTML生成DOM树 -
DOM树的构建过程:字节 -> 字符 -> 标记(token)-> DOM节点对象 -> DOM对象模型
-
-
构建样式树
-
浏览器无法解析纯文本的 CSS 样式,需要对 CSS 进行解析,生成CSSOM树。 -
CSSOM树的构建过程:字节 -> 字符 -> 标记(token) -> CSS节点对象 -> CSS对象模型 -
计算出 DOM 树中每个节点的具体样式(Attachment)(打开浏览器Elements标签,查看元素格式化好的样式)
-
-
创建渲染树
-
将DOM树和CSSOM树这两个对象集合结合一起,形成渲染树
-
-
布局
-
从render树的根节点开始遍历,计算节点渲染到页面的坐标位置。(reflow回流)
-
-
绘制
-
根据渲染树以及回流得到的几何信息,得到节点的绝对像素。(Painting 重绘)
-
-
分层
-
通过以下几个常用属性可以生成新图层 -
3D 变换:translate3d、translateZ; -
video、iframe 标签;
-
-
通过动画实现的 opacity 动画转换; -
position: fixed;
-
-
显示
-
浏览器会将各层的信息发送给GPU,GPU会将各层合成,将内容显示在页面上。
-
渲染完毕后就是load事件了,之后就是自己的JS逻辑处理了。
输入url到整个页面发生了什么
最后我们来看一个经典问题,输入url到整个页面发生了什么?
-
用户输入url,浏览器进程开始处理 -
如果是关键词,会使用默认搜索引擎合成带有搜索关键字的URL; -
如果是URL,或者是符合url规则的的内容,会带上协议合成完整的URL。
-
-
URL请求过程: -
网络进程先对本地进行判断是否有缓存,有的话直接将资源返回给浏览器进程,没有的话进行下一步。 -
DNS通过域名解析得到IP地址。 -
将IP地址和服务器TCP连接,三次握手。 -
服务器收到请求的信息后,会根据请求的信息生成响应头,响应行,响应体,然后发送给网络进程,网络进程接受了响应的信息后,就开始解析响应头的内容。 -
解析的过程:有可能进行重定向(状态码301,302时),重新发起请求。 -
浏览器通过请求头的Content-type字段判断如何显示响应体的内容,比如是text/html,返回的是html格式,浏览器通知渲染进程,要开始干活了。
-
-
渲染进程开始准备 -
提交文档 -
渲染进程准备好后,浏览器进程发出提交文档的消息 -
渲染进程接收消息 -
跟网络进程建立传输数据的通道 -
数据传输完成后,渲染进程会告诉浏览器进程,确认文档提交,浏览器更新页面。
-
作者简介
王胜开:管住嘴,迈开腿,准备减肥
作者介绍
