选自:
简述浏览器渲染机制
一 : 为什么要了解浏览器渲染页面的机制
目的:主要还是性能的优化。
- 了解浏览器如何进行加载,我们可以在引用外部样式文件,外部JS时,将它们放到合适的位置,是浏览器以最快的速度,将文件加载完毕。
- 了解浏览器如何进行解析,我们可以在构建DOM结构,组织CSS选择器的时候,选择最优的写法,提高浏览器的解析速率。
- 了解浏览器如何进行渲染,明白渲染的过程,我们在设置元素属性,编写JS文件时,可以减少“重绘”,“重新布局”的消耗。
二 :基本概念
要了解清楚渲染机制,要先弄明白几个基本概念:
- DOM(DOM树):Document Object Model,浏览器将HTML解析成树形的数据结构,简称DOM。
- CSSOM(CSS树):CSS Object Model,浏览器将CSS解析成树形的数据结构,简称CSSOM。
- Render Tree(渲染树): DOM和CSSOM合并后生成Render
Tree,如下图:
- Layout: 计算出Render Tree每个节点的具体位置。
- Painting:通过显卡,将Layout后的节点内容分别呈现到屏幕上。
三 : 需要注意的是:
- 当我们浏览器获得HTML文件后,会自上而下的加载,并在加载过程中进行解析和渲染。
- 加载说的就是获取资源文件的过程,如果在加载过程中遇到外部CSS文件和图片,浏览器会另外发送一个请求,去获取CSS文件和相应的图片,这个请求是异步的,并不会影响HTML文件的加载。(写在HTML文件里面的css代码不会发生请求)
- 但是!如果遇到Javascript文件,HTML文件会挂起渲染的进程,等待JavaScript文件加载完毕后,再继续进行渲染。
- 为什么HTML需要等待JavaScript呢?因为JavaScript可能会修改DOM,导致后续HTML资源白白加载,所以HTML必须等待JavaScript文件加载完毕后,再继续渲染,这也就是为什么
****JavaScript文件在写在底部body标签前****
的原因。
四 : 浏览器渲染的整个流程
浏览器整个流程如上图所示:
当用户输入一个URL时,浏览器就会向服务器发出一个请求,请求URL对应的资源
- 接受到服务器的响应内容后,浏览器的HTML解析器,会将HTML文件解析成一棵DOM树,DOM树的构建是一个深度遍历的过程,当前节点的所有子节点都构建完成以后,才会去构建当前节点的下一个兄弟节点。
- 将CSS解析成CSSOM树(CSS Rule Tree)
- 根据DOM树和CSSOM树,来构建Render Tree(渲染树),注意渲染树,并不等于DOM树,因为一些像head或display:none的东西,就没有必要放在渲染树中了。
- 有了Render Tree,浏览器已经能知道网页中有哪些节点,各个节点的CSS定义,以及它们的从属关系,下一步操作就是Layout,顾名思义,就是计算出每个节点在屏幕中的位置。
- Layout后,浏览器已经知道哪些节点要显示,每个节点的CSS属性是什么,每个节点在屏幕中的位置是哪里,就进入了最后一步painting,按照算出来的规则,通过显卡,把内容画到屏幕上。
即
- 根据服务器发送的HTML生成dom树,解析CSS文件生成CSSOM树
- 结合DOM树和CSSOM树生成render树,也就是渲染树
- 在render树中对每个节点进行布局,计算每个元素的大小,确定其在屏幕中的位置
- 绘制。根据render树和布局将显示页面
这里还要说两个概念,一个是Reflow,另一个是Repaint。这两个不是一回事。
- Repaint ——改变某个元素的背景色、文字颜色、边框颜色(即颜色)等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
- Reflow ——元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。
- reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
注:display:none会触发reflow(可能全部重绘),而visibility:hidden只会触发repaint(一部分改变),因为没有发现位置变化。
以上为转载,简述浏览器渲染机制
浏览器的加载、解析时间线
js 时间线步骤(创建 document 对象==>文档解析完==>文档解析完加载完执行完)
-
创建 Document 对象
,开始解析 web 页面。解析 HTML 元素和他们的文本内容
后添加 Element 对象和 Text 节点到文档中。这个阶段document.readyState = 'loading'。
- 遇到 link 外部 css,创建线程,进行异步加载,并继续解析文档。
- 遇到 script 外部 js,并且没有设置 async、defer,浏览器同步加载,并阻塞,等待 js 加载完成并执行该脚本,然后继续解析文档。
4、遇到 script 外部 js,并且设置有 async、defer,浏览器创建线程异步加载,并继续解析文档。对于 async 属性的脚本,脚本加载完成后立即执行。(异步禁止使用 document.write()
,因为当你整个文档解析到差不多,再调用 document.write(),会把之前所有的文档流都清空,用它里面的文档代替)
5、遇到 img 等(带有 src),先正常解析 dom 结构,然后浏览器异步加载 src,并继续解析文档。 看到标签直接生产 dom 树,不用等着 img 加载完 src。
6、当文档解析完成(domTree 建立完毕,不是加载完毕),document.readyState = 'interactive'
。
7、文档解析完成后,所有设置有 defer 的脚本会按照顺序执行
。(注意与 async 的不同,但同样禁止使用 document.write());
8、document 对象触发 DOMContentLoaded 事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
9、当所有 async 的脚本加载完成并执行后、img 等加载完成后(页面所有的都执行加载完之后),document.readyState = 'complete'
,window 对象触发 load 事件。
10、从此,以异步响应方式处理用户输入、网络事件等。
通用写法是把 JS 的 script 写在最下面