浏览器渲染原理

一、介绍

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

 

2.浏览器的工作大体流程如下

浏览器渲染原理_第1张图片

 

所以我们可以看到浏览器会解析三个东西

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

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

  3. JavaScript,脚本,通过DOM API 和CSSOM API来操作dom树和css rule tree

 

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

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

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

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

 

 

下面来详细了解这些过程

1.构建DOM

浏览器会遵守一套步骤将HTML文件转换为DOM树,宏观上,可以视为以下一个步骤

浏览器渲染原理_第2张图片

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

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

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

构建DOM的过程并不是等所有的TOKEN都转换完成再去生成节点对象,而是一边生成token一边消耗token来生成节点对象。换句话说,每个token被生成之后,会;立刻消耗这个token所生成的节点对象。

例如下面的代码就会生成解析成下面这样的情况

    this is web page

    

        

Web page parsing

        

hello world

    

 

浏览器渲染原理_第3张图片

 

 

2.构建CSSDOM

 

DOM会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建CSSOM。构建CSSOM的过程与构建DOM的过程非常相似,当浏览器接收到一段CSS,浏览器首先要识别出token,然后构建节点并生成CSSOM。

浏览器渲染原理_第4张图片

在这个过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为你想,样式除了自己设置之外还会继承其祖先元素以及父元素的样式,在这个过程中,浏览器得递归遍历CSSOM数,然后才能确定元素具体的样式是什么。

 

CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。

 

 

3.构建渲染树

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

浏览器渲染原理_第5张图片

 

 

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

 

 

4.布局(layout)和绘制

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

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

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

 

 

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

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

一、当渲染的时候遇到了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。

 

 

二、回流和重绘是什么?

 

浏览器渲染原理_第6张图片

 

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

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

因此得出了一个结论:

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

 

 

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

 

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

 

浏览器渲染原理_第7张图片

 

触发重绘的属性有

浏览器渲染原理_第8张图片

 

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 标签来说,浏览器会自动将该节点变为图层。

 

三、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是有顺序的加载。

 

 

 

四、为什么操作dom慢

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

 

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

由于浏览器的渲染机制不同,在渲染页面时会出现两种常见的不良现象----白屏问题和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 标签底部。

 

 

 

 

 

你可能感兴趣的:(前端)