浏览器渲染原理

1、介绍css

1.浏览器的内核包括两个部分,一个是渲染引擎,另外一个是js引擎。渲染引擎在每一个浏览器中不必定相同,例如firefox中叫作Gecko,chrome和safari的是webkit,ie的是trident等。国内浏览器的内核通常都是ie的内核trident。html

 

2.浏览器的工做大致流程以下web

 

因此咱们能够看到浏览器会解析三个东西chrome

  1. html/svg/xhtml,渲染引擎解析这三种文件,产生一个DOM tree浏览器

  2. css ,解析css会产生css规则树。网络

  3. JavaScript,脚本,经过DOM API 和CSSOM API来操做dom树和css rule treedom

 

解析完成后,浏览器引擎就会经过DOM TREE和CSS RULE TREE来构造RENDER TREE(渲染树)异步

  • render 树不等同于DOM树,由于一些像Header或display:none的东西就不必放在渲染树中了。async

  • CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每一个Element。也就是DOM结点。也就是所谓的Frame。ide

  • 而后,计算每一个Frame(也就是每一个Element)的位置,这又叫layout和reflow过程。

 

 

下面来详细了解这些过程

1.构建DOM

浏览器会遵照一套步骤将HTML文件转换为DOM树,宏观上,能够视为如下一个步骤

  1. 浏览器从本地磁盘或者网络读取HTML的原始字节,并根据文件的指定编码(例如UTF-8)将它们转换为字符串。{在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据之后,它会将这些字节数据转换为字符串,也就是咱们写的代码。}

  2. 将字符串转换成TOKEN,例如<html><head>等,token会标识出当前token是开始标签或是结束标签亦或是文本等信息,也就是说token会识别出节点之间的父子关系亦或是兄弟关系。

  3. 生成节点对象并构建DOM

构建DOM的过程并非等全部的TOKEN都转换完成再去生成节点对象,而是一边生成token一边消耗token来生成节点对象。换句话说,每一个token被生成以后,会;马上消耗这个token所生成的节点对象。

例以下面的代码就会生成解析成下面这样的状况

<html>

<head>

    <title>this is web page</title>

</head>

<body>

    <div>

        <h1>Web page parsing</h1>

        <p>hello world</p>

    </div>

</body>

</html>

 

 

 

2.构建CSSDOM

 

DOM会捕获页面的内容,但浏览器还须要知道页面如何展现,因此须要构建CSSOM。构建CSSOM的过程与构建DOM的过程很是类似,当浏览器接收到一段CSS,浏览器首先要识别出token,而后构建节点并生成CSSOM。

在这个过程当中,浏览器会肯定下每个节点的样式究竟是什么,而且这一过程实际上是很消耗资源的。由于你想,样式除了本身设置以外还会继承其祖先元素以及父元素的样式,在这个过程当中,浏览器得递归遍历CSSOM数,而后才能肯定元素具体的样式是什么。

 

CSS匹配HTML元素是一个至关复杂和有性能问题的事情。因此,DOM树要小,CSS尽可能用id和class,千万不要过渡层叠下去。

 

 

3.构建渲染树

当咱们生成DOM树和CSSOM树之后,就要将这两棵树组合渲染成为渲染树。

 

 

在这一过程当中,不是简单的将二者合并就好了。渲染树只会包括须要显示的节点和这些节点的样式信息,若是某个节点是 display: none 的,那么就不会在渲染树中显示。

 

 

4.布局(layout)和绘制

当浏览器生成渲染树以后,就会根据渲染树来进行布局(也能够叫作回流)。这一个阶段浏览器要作的事情就是要弄清楚每一个节点在页面中的确切位置和大小。一般这一个行为也叫作自动重排。

布局流程的输出是一个盒模型,它会精确的捕获每一个元素在视口内的确切位置和尺寸,全部相对测量值都将转换成屏幕上的绝对像素。

布局完成以后,浏览器就会当即发出Paint Setup 和Paint事件,将渲染后转换成屏幕上的像素。

 

 

上面就是浏览器的渲染过程了。

下面来讲说几个比较重要的问题

1、当渲染的时候遇到了js文件要怎么处理?

JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若是遇到了JavaScript,那么它会暂停构建DOM,将控制权交给JavaScript引擎。(从渲染引擎交给了js引擎)。等JavaScript引擎运行完毕,浏览器再从终端的地方回复DOM的构建。

也就是说,若是须要首屏渲染的越快,就更不该搞在首屏就加载js文件,这也是建议script标签放在body标签底部的缘由。固然在当下,并非说script标签必须放在底部,由于你能够给script加上defer和async属性。

 

js不仅阻塞dom的构建,也会致使CSSOM也阻塞DOM的构建。

 

本来DOM和CSSOM之间的构建互不影响,可是一旦引入了js,cssom就会开始阻塞dom的构建,只有cssom构建完毕后,dom再恢复dom构建。

这是由于JavaScript不仅是能够更改dom,还能够更改样式,也就是它能够更改cssom。

不完整的CSSOM是没法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必需要能拿到完整的CSSOM。因此就致使了一个现象,若是浏览器还没有完成CSSOM的下载和构建,而咱们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种状况下,浏览器会先下载和构建CSSOM,而后再执行JavaScript,最后在继续构建DOM。

 

 

2、回流和重绘是什么?

 

 

回流:当render tree 的一部分或所有的元素因改变了自身的宽高,布局,显示或隐藏,或者元素内部的文字结构发生变化 致使须要从新构建页面的时候,回流就产生了。

重绘:当一个元素自身的宽高,布局,及显示或隐藏没有改变,而只是改变了元素的外观风格的时候,就会产生重绘。例如你改变了元素的background-color....。

所以得出了一个结论:

回流一定触发重绘,而重绘不必定触发回流

 

 

咱们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程当中,还会不断从新渲染。从新渲染会重复上图中的第四步(回流)(渲染树)+第五步(重绘)(painting)或者只有第五个步(重绘)。

 

1)引发回流的属性和方法

 

 

触发重绘的属性有

 

2)如何减小回流、重绘

使用 transform 替代 top

使用 visibility 替换 display: none ,由于前者只会引发重绘,后者会引起回流(改变了布局)

不要把节点的属性值放在一个循环里当成循环里的变量。

for(let i = 0; i < 1000; i++) {

    // 获取 offsetTop 会致使回流,由于须要去获取正确的值

    console.log(document.querySelector('.test').style.offsetTop)

}

不要使用 table 布局,可能很小的一个小改动会形成整个 table 的从新布局

动画实现的速度的选择,动画速度越快,回流次数越多,也能够选择使用 requestAnimationFrame

CSS 选择符从右往左匹配查找,避免节点层级过多

将频繁重绘或者回流的节点设置为图层,图层可以阻止该节点的渲染行为影响别的节点。好比对于 video 标签来讲,浏览器会自动将该节点变为图层。

 

3、async和defer的做用是什么?有什么区别

1.没有derfer和async,浏览器会当即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

 

2.有async,异步下载

async 属性表示异步执行引入的 JavaScript(加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行)与 defer 的区别在于,若是已经加载好,就会开始执行——不管此刻是 HTML 解析阶段仍是 DOMContentLoaded 触发以后。须要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发以前或以后执行,但必定在 load 触发以前执行。

 

3.有defer,延迟执行

defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未中止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成以后(这两件事情的顺序无关),会执行全部由 defer-script 加载的 JavaScript 代码,而后触发 DOMContentLoaded 事件。defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成以后。 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。

 

 

 

4、为何操做dom慢

由于 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当咱们经过 JS 操做 DOM 的时候,其实这个操做涉及到了两个线程之间的通讯,那么势必会带来一些性能上的损耗。操做 DOM 次数一多,也就等同于一直在进行线程之间的通讯,而且操做 DOM 可能还会带来重绘回流的状况,因此也就致使了性能上的问题。

 

5、渲染页面的时候有哪些不良的现象

因为浏览器的渲染机制不一样,在渲染页面时会出现两种常见的不良现象----白屏问题和FOUS(无样式内容闪烁)

FOUC:因为浏览器渲染机制(好比firefox),在CSS加载以前,先呈现了HTML,就会致使展现出无样式内容,而后样式忽然呈现的现象;

白屏:有些浏览器渲染机制(好比chrome)要先构建DOM树和CSSOM树,构建完成后再进行渲染,若是CSS部分放在HTML尾部,因为CSS未加载完成,浏览器迟迟未渲染,从而致使白屏;也多是把js文件放在头部,脚本会阻塞后面内容的呈现,脚本会阻塞其后组件的下载,出现白屏问题。

 

 

总结

* 浏览器工做流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。

* CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。

* 一般状况下DOM和CSSOM是并行构建的,可是当浏览器遇到一个script标签时,DOM构建将暂停,直至脚本完成执行。但因为JavaScript能够修改CSSOM,因此须要等CSSOM构建完毕后再执行JS。

* 若是你想首屏渲染的越快,就越不该该在首屏就加载 JS 文件,建议将 script 标签放在 body 标签底部。