一、了解浏览器的意义
如果不了解浏览器的运行原理,浏览器对于我们来说就是一个黑盒,所以我们的代码最终运行效果怎样、如何优化web应用的性能、如何技术选型等问题,都是我们建立在经验之上,难以形成体系。
了解浏览器原理能让你把网络、网页解析、页面渲染、JavaScript等知识串联起来,让你对前端知识有一个全面的认识。
二、Chrome架构
历史
2008年之前浏览器(IE)都是单进程架构的,这波浏览器的效率不高,容易崩溃,已成为不堪回首的往事。之后Chrome浏览器横空出世,采用多进程的架构,速度和安全性碾压全场,Chorme市场份额剧增,奠定了现在的老大地位。单进程的劣势
不稳定,单进程浏览器的插件各种各样,质量参差不齐很容易内存泄漏和引发崩溃。另外渲染引擎执行JS代码的时候也很容易因为错误的JS代码而崩溃。单进程的浏览器一处崩溃就是整个浏览器崩溃的结果,造成浏览器很不稳定,想象一下愉快的偷菜中,浏览器崩溃了,我崩溃了。
不安全, 单进程浏览器模块们都是运行在线程中的,而线程有能力通过通过漏洞控制浏览器进程获取到操作系统的任何资源。比如恶意的插件,页面中恶意的js脚本。
不流畅, 由于是单进程的,所有页面的js只能在一条线程上执行,其中一个页面js执行时卡住了线程其他页面的js就得不到执行,页面失去响应,用户暴走。
内存泄漏, 单进程浏览器页面js执行过程中造成的内存泄漏,在页面关闭的时候并不会得到很好的清理,这样打开的页面多了,时间长了内存占用很高,造成卡顿。
-
多进程的优势
多进程架构
- 稳定,由于每个页面有自己独立的渲染进程,每个插件运行在自己的独立进程。进程互相独立,所以某个页面或者插件的崩溃不会导致整个浏览器的崩溃。稳!
安全,Chrome渲染进程和插件进程运行在沙箱环境,他们只能访问自己内部的数据,且不能再硬盘上写如数据,也不能读取敏感数据。这样即使执行了恶意程序,有沙箱的保护,也不能获取到系统权限。
流畅,由于页面运行在自己的渲染进程中,有独立的js线程,当前页面的js执行卡住线程并不会影响到其他页面的js执行。
内存,页面关闭,它的渲染进程也会被关闭,进程的内存由系统收回,不会造成内存泄漏。
- 多进程浏览器的缺点
资源占用,由于每个进程有复制的基础结构(比如js环境等),多进程会占用更多的资源。此处提醒惠老板
复杂的体系,多进程架构的各个模块之间的耦合性高,比较复杂。
三、页面导航
从浏览器地址栏输入地址敲回车到页面呈现给我们,中间发生了什么?
- 处理输入
- 首先是用户输入关键词,浏览器会判断这个输入的关键字是搜索内容,还是请求的 URL。
- 如果是搜索内容则丢给浏览器默认搜索引擎处理。
- 如果是url就补齐协议,给当前页面一个 beforeunload 事件,在这个事件中可以询问用户是否要离开当前页面。
- 如果没有监听这个事件,或者用户同意跳转就发出请求
- 发起请求
- 便签图标开始转圈
- 首先是查询本地缓存,如果有缓存就直接返回对应的资源。
- 本地没有缓存,那么就要正式发起请求。先是DNS解析,得到IP地址然后建立TCP连接。
- 构建请求头 ,请求行 发起请求。
- 处理响应
- 接收到服务器的响应头,解析响应头内容
- 如果响应头状态码是301、302,说明需要重定向,此时提响应头中的 Location 字段,获取重定向地址,重新发起http请求。
- 如果状态码200,表示一切正常,继续往下解析。
- 判断content-type,他告诉浏览器响应体是一个什么类型的数据,浏览器根据这个字段会做不同的处理。如果值是 application/octet-stream 表示这是一个字节流,浏览器会交给下载器处理。如果是text/html就是告诉浏览器这是一个网页文档
- 准备渲染进程
- 判断当前页面和目标页面是否是同一站点,就是说当前页面和目标页面的协议(http/https)和根域名都相同
- 如果打开的新页面和父页面是同一站点,那么两个页面共用一个渲染进程,否则会为新页面创建一个新的进程。
- 提交文档
- 前面渲染进程创建完毕后,浏览器进程会向渲染进程发提交文档的消息,让渲染进程准备接收文档数据。
- 渲染进程接收到这个消息之后,会和网络进程建立传输通道,数据就和水一样从管子里面流入渲染进程。
- 当渲染进程接收数据完毕之后,会向浏览器进程发送一个“确认提交”的消息,意思是文档都收到了,可以开始显示页面了。
- 浏览器接收到“确认提交”消息之后开始更新界面状态,包括前进后退按钮,地址栏,页面菊花停止等。这也就解释了为什么不是输入地址按下回车之后,页面没有马上消失,而是要等一会才会更新的原因。
四、页面渲染流水线
渲染过程会分为很多子阶段,每个阶段都有输入,处理过程,输出。
(1)构建DOM树
浏览器无法直接理解和使用HTML文档,需要将HTML转化成浏览器能够识别的DOM树结构。当上文提到的浏览器进程向渲染进程发出“提交文档”消息,渲染进程开始从网络进程接收数据,此时DOM解析也随之开始了。。。。
// 示例代码
<html>
<body>
<div>1</div>
<div>test</div>
</body>
</html>
预解析器,当渲染进程开始接收HTML文档之后,会开启一个预解析器线程,用来分析HTML文档中包含的JavaScript和CSS文件,提前下载。DOM的解析是依赖JavaScript和CSS的,预下载有利于DOM的解析,加速页面的宣传进度。
-
分词
通过分词器将字节流分成一个一个token,分别是标签Token 和 文本Token。其中标签token又分为开始标签token (比如:<div>),和结束标签token (比如 </div>)。
生成 token.png 将token转换为DOM节点,并添加到DOM树中
HTML解析器维护了一个Token栈,用于维护节点之间的父子关系。
之前分词得到的token会按序压入dom栈中。在这个压栈的过程中有如下的规则
将一个开始节点压入栈中,HTML解析器会为这个Token创建一个DOM节点,并将该节点加入DOM树中,他的父节点就是栈中相邻的那个token生成的节点。
如果是文本token,那么无需入栈,直接创建一个文本节点加入DOM数中,他的父节点就是此时栈顶的那个Token对应的节点
如果是结束标签,那么会检查此时栈顶的标签是否是对应的开始标签,如果是就将栈顶的Token出栈,这个元素解析完成了。这样进进出出的不断压栈和出栈,直到整个字节流处理完成。
(2)样式计算
样式计算的目的是计算出DOM中每个元素的具体样式。大体上分为3个阶段
- 解析得到CSSOM
和HTML一样,浏览器也不能理解CSS文件,需要将这个CSS文件解析转换成浏览器能理解的结构CSSOM。CSSOM和DOM一样有两个作用。
- 提供给JavaScript操纵样式表的能力
- 为布局数合成提供样式信息
标准化CSS样式中的属性值
CSS样式中有一些浏览器不容易理解的属性值,比如 em blod等,需要将这些值抓换为浏览器容易理解的,标准化的计算值。计算DOM中每个节点的样式
根据CSS 的继承性,和层叠性计算出DOM中每个元素的具体样式。
(3)布局计算
现在我们有了DOM 和 CSSOM了,但是这不足以显示页面,我们还要知道每个节点的位置信息,接下来就是要计算出每个节点的位置信息,
创建布局树
DOM中包含了一些不可见的元素,包括header标签下面的内容,还要display:none的一些元素。遍历DOM中的节点,把不可见的元素剔除,生成了布局树的主体。布局计算
根据节点样式等信息计算出每个节点的坐标位置,然后再写入布局树中。
(4)分层
- 为什么要分层?
由于页面中有很多复杂的显示,比如3D变换、滚动、zIndex处理等,为了更方便的实现这些效果,渲染引擎要为特定的一些节点生成专用的图层,组成一个层树
- 节点什么情况下会提升到层
通常有两种情况会被提升到独立的层,看图
- 拥有层叠上下文属性 (透明度,CSS滤镜等)
- 需要裁减的地方
(5)图层绘制
渲染引擎把每个图层转化为一条条绘制指令,再把这些绘制指令按序组成绘制表,看图
(6)栅格化
- 当绘制表完成之后,主线程会把他commit给合成线程。
- 合成线程会把图层分为很多的块,一般是256256或者512512的
- 根据视口,合成线程会优先把视口范围的块交给栅格化线程池处理。
- 栅格化线程池会把这些块交给GPU进程处理,将这些图块转换成位图,然后存储在GPU的缓存里面。
(7)合成
1.当光栅化完成,每个图层会被绘制成一张图片,合成线程拥有了这些图片,用Chrom合成器把多个图层的图片合成一张。
- 合成线程会生成绘制图块的命令DrawQuad,并向浏览器发送这个命令
- 浏览器接收到这个DrawQuad命令,将其中合成后的图片放入后缓冲区,等待显示器的提取。
(8)重排、重绘、合成
1.重排
改变元素的几何属性,引发重新布局
- 重绘
改变元素背景属性,引发重绘,跳过布局阶段,直接进入绘制阶段开始
- 合成
例如使用transform来执行动画效果,没有改变元素的布局属性,也没有改变元素的绘制属性。跳过布局和绘制阶段,直接进入合成阶段。合成是在合成线程执行,不会影响主线程,大大提高了效率。
最后
了解了浏览器原理有没有让你豁然开朗?对于性能优化有没有新的感悟?希望这篇笔记能对你有所启发,谢谢。