其实就是一堆HMTL格式的字符串,因为只有HTML格式浏览器才能正确解析,这是W3C标准的要求。接下来就是浏览器的渲染过程。
浏览器渲染过程
浏览器渲染过程大体分为如下三部分:
1)浏览器会解析三个东西:
一是HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构。
二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像。
三是脚本,等到脚本文件加载后,通过DOMAPI和来操作DOMTree和。
2)解析完成后,浏览器引擎会通过DOMTree和来构造。
3)最后通过调用操作系统的API绘制。
接下来我们针对这其中所经历的重要步骤详细阐述
构建 DOM
浏览器会遵守一套步骤将HTML文件转换为DOM树。宏观上,可以分为几个步骤:
浏览器从磁盘或网络读取HTML的原始字节,并根据文件的指定编码(例如UTF-8)将它们转换成字符串。
在网络中传输的内容其实都是0和1这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为字符串,也就是我们写的代码。
将字符串转换成Token,例如:、等。Token 中会标识出当前 Token 是“开始标签”或是“结束标签”亦或是“文本”等信息。
这时候你一定会有疑问,节点与节点之间的关系如何维护?
事实上,这就是Token要标识“起始标签”和“结束标签”等标识的作用。例如“title”Token的起始标签和结束标签之间的节点肯定是属于“head”的子节点。
上图给出了节点之间的关系,例如:“Hello”Token位于“title”开始标签与“title”结束标签之间,表明“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点。
事实上,构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每个Token被生成后,会立刻消耗这个Token创建出节点对象。注意:带有结束标签标识的Token不会创建节点对象。
接下来我们举个例子,假设有段HTML文本:
Web page parsing
Web page parsing
This is an example Web page.
上面这段HTML会解析成这样:
构建 CSSOM
DOM会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建CSSOM。
构建CSSOM的过程与构建DOM的过程非常相似,当浏览器接收到一段CSS,浏览器首先要做的是识别出Token,然后构建节点并生成CSSOM。
在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归CSSOM树,然后确定具体的元素到底是什么样式。
注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。
构建渲染树
当我们生成DOM树和CSSOM树以后,就需要将这两棵树组合为渲染树。
在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display:none的,那么就不会在渲染树中显示。
我们或许有个疑惑:浏览器如果渲染过程中遇到 JS 文件怎么处理?
渲染过程中,如果遇到
没有defer或async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
情况2: (异步下载)
async属性表示异步执行引入的,与defer的区别在于,如果已经加载好,就会开始执行——无论此刻是HTML解析阶段还是触发之后。需要注意的是,这种方式加载的依然会阻塞load事件。换句话说,async-script可能在触发之前或之后执行,但一定在load触发之前执行。
情况3:(延迟执行)
defer属性表示延迟执行引入的,即这段加载时HTML并未停止解析,这两个过程是并行的。整个解析完毕且defer-script也加载完成之后(这两件事情的顺序无关),会执行所有由defer-script加载的代码,然后触发事件。
defer与相比普通script,有两点区别:载入文件时不阻塞HTML的解析,执行阶段被放到HTML标签解析完成之后。在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。
2.为什么操作DOM慢?
把DOM和各自想象成一个岛屿,它们之间用收费桥梁连接。——《高性能》
JS是很快的,在JS中修改DOM对象也是很快的。在JS的世界里,一切是简单的、迅速的。但DOM操作并非JS一个人的独舞,而是两个模块之间的协作。
因为DOM是属于渲染引擎中的东西,而JS又是JS引擎中的东西。当我们用JS去操作DOM时,本质上是JS引擎和渲染引擎之间进行了“跨界交流”。这个“跨界交流”的实现并不简单,它依赖了桥接接口作为“桥梁”(如下图)。
过“桥”要收费——这个开销本身就是不可忽略的。我们每操作一次DOM(不管是为了修改还是仅仅为了访问其值),都要过一次“桥”。过“桥”的次数一多,就会产生比较明显的性能问题。因此“减少DOM操作”的建议,并非空穴来风。
3.你真的了解回流和重绘吗?
渲染的流程基本上是这样(如下图黄色的四个步骤):
1. 计算 CSS 样式
2. 构建 Render Tree
3.Layout – 定位坐标和大小
4. 正式开画
注意:上图流程中有很多连接线,这表示了动态修改了DOM属性或是CSS属性会导致重新Layout,但有些改变不会重新Layout,就是上图中那些指到天上的箭头,比如修改后的CSSrule没有被匹配到元素。
这里重要要说两个概念,一个是 Reflow,另一个是 Repaint
重绘:当我们对DOM的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。
回流:当我们对DOM的修改引发了DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来,这个过程就是回流(也叫重排)。
我们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染。重新渲染会重复回流+重绘或者只有重绘。
回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
1)常见引起回流属性和方法
任何会改变元素几何信息 (元素的位置和尺寸大小) 的操作,都会触发回流,
2)常见引起重绘属性和方法
3)如何减少回流、重绘
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
性能优化策略
基于上面介绍的浏览器渲染原理,DOM和CSSOM结构构建顺序,初始化可以对页面渲染做些优化,提升页面性能。
JS优化:
*请认真填写需求信息,我们会在24小时内与您取得联系。