目录

  1. 为什么要了解浏览器加载、解析、渲染这个过程
  2. 浏览器的主要功能
  3. 浏览器的渲染
    1. 关键渲染路径
      1. 构建DOM树
      2. 构建CSSOM规则树
      3. 渲染阻塞
      4. 构建渲染树
      5. 渲染树布局
      6. 渲染树绘制
    2. 重点
      1. reflow
      2. 减少reflow/repaint
      3. 注意
      4. HTML页面加载和解析流程
      5. 编写CSS时应该注意
      6. 关于Script标签位置
      7. 减少JavaScript对性能的影响

转载:
  浏览器加载、解析、渲染的过程
  【干货】十分钟读懂浏览器渲染流程

为什么要了解浏览器加载、解析、渲染这个过程

  了解浏览器如何进行加载,可以在引用外部样式文件,外部js时,将他们放到合适的位置,使浏览器以最快的速度将文件加载完毕。
  了解浏览器如何进行解析,可以在构建DOM结构,组织css选择器时,选择最优的写法,提高浏览器的解析速率。
  了解浏览器如何进行渲染,明白渲染的过程,在设置元素属性,编写js文件时,可以减少”reflow“”repaint“的消耗。

浏览器的主要功能

  浏览器的主要功能是将用户选择的web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI(Uniform Resource Identifier统一资源标识符)来指定所请求资源的位置,通过DNS查询,将网址转换为IP地址。整个浏览器工作的流程为:
  1、 输入网址。
  2、 浏览器查找域名的IP地址。
  3、 浏览器给web服务器发送一个HTTP请求
  4、 网站服务的永久重定向响应
  5、 浏览器跟踪重定向地址。现在,浏览器知道了要访问的正确地址,所以它会发送另一个获取请求。
  6、 服务器“处理”请求,服务器接收到获取请求,然后处理并返回一个响应。
  7、 服务器发回一个HTML响应
  8、 浏览器开始显示HTML
  9、 浏览器发送请求,以获取嵌入在HTML中的对象。在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。这些文件就包括CSS/JS/图片等资源,这些资源的地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等…

  那么,一个页面,究竟是如何从我们输入一个网址到最后完整的呈现在我们面前的呢?还需要了解一下浏览器是如何渲染的:

浏览器的渲染

渲染引擎在取得内容之后的基本流程:
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
所以,浏览器会解析三个东西:
(1) HTML/SVG/XHTML,解析这三种文件会产生一个 DOM Tree。
(2) CSS,解析 CSS 会产生 CSS 规则树。
(3) Javascript脚本,主要是通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree.

关键渲染路径

  关键渲染路径是指浏览器从最初接收请求来的HTML、CSS、javascript等资源,然后解析、构建树、渲染布局、绘制,最后呈现给客户能看到的界面这整个过程。

当浏览器获得一个html文件时,会“自上而下”加载,并在加载过程中进行解析渲染。
解析:
  1、 DOM Tree: 浏览器会将HTML解析成一个DOM树。DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
  2、 CSS Rule Tree: 将CSS解析成 CSS Rule Tree 。
  3、 Render Tree: 根据DOM树和CSSOM来构造 Rendering Tree。
  注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header
  或 display:none 的东西就没必要放在渲染树中了。
  4、 layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。 (遍历渲染树开始布局,计算每个节点的位置大小信息
  5、 painting: 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。
  (将渲染树每个节点绘制到屏幕。)

构建DOM树

  当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,生成DOM树。
需要注意的是,DOM树的生成过程中可能会被CSS和JS的加载执行阻塞。渲染阻塞问题下文会讲。

构建CSSOM规则树

  浏览器解析CSS文件并生成CSS规则树,每个CSS文件都被分析成一个StyleSheet对象,每个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其他对象。

渲染阻塞

  当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行,然后继续构建DOM。每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。
  所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:
CSS 优先:引入顺序上,CSS 资源先于 JavaScript资源。
JS置后:我们通常把JS代码放到页面底部,且JavaScript 应尽量少影响 DOM 的构建。当解析html的时候,会把新来的元素插入dom树里面,同时去查找css,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序去匹配的。例如: div p {font-size: 16px},会先寻找所有p标签并判断它的父标签是否为div之后才会决定要不要采用这个样式进行渲染)。所以,我们平时写CSS时,尽量用id和class,千万不要过渡层叠。

构建渲染树

  通过DOM树和CSS规则树我们便可以构建渲染树。浏览器会先从DOM树的根节点开始遍历每个可见节点。对每个可见节点,找到其适配的CSS样式规则并应用。渲染树构建完成后,每个节点都是可见节点并且都含有其内容和对应规则的样式。这也是渲染树与DOM树的最大区别所在。渲染树是用于显示,那些不可见的元素当然就不会在这棵树中出现了,譬如。除此之外,display等于none的也不会被显示在这棵树里头,但是visibility等于hidden的元素是会显示在这棵树里头的。

渲染树布局

  布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。

渲染树绘制

  在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。

重点

  上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
几个概念:
  (1)Reflow(回流):浏览器要花时间去渲染,当它发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。
  (2)Repaint(重绘):如果只是改变了某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的repaint,重画某一部分。
Reflow要比Repaint更花费时间,也就更影响性能。所以在写代码的时候,要尽量避免过多的Reflow。

reflow

  (1)页面初始化的时候;
  (2)操作DOM时;
  (3)某些元素的尺寸变了;
  (4)如果 CSS 的属性发生变化了。

减少reflow/repaint

  (1)不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className。
  (2)不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
  (3)为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
  (4)千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

注意

注意:
   (1)display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。
   (2)display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。
   (3)有些情况下,比如修改了元素的样式,浏览器并不会立刻reflowrepaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow

HTML页面加载和解析流程
  1. 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
  2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;
  3. 浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
  4. 浏览器继续载入html<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;
  5. 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
  6. 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
  7. 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;
  8. Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。突然少了这么一个元素,浏览器不得不重新渲染这部分代码;
  9. 终于等到了</html>的到来,浏览器泪流满面……
  10. 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;
  11. 浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。
编写CSS时应该注意

  CSS选择符是从右到左进行匹配的。从右到左!所以,#nav li 我们以为这是一条很简单的规则,秒秒钟就能匹配到想要的元素,但是,但是,但是,是从右往左匹配啊,所以,会去找所有的li,然后再去确定它的父元素是不是#nav。,因此,写css的时候需要注意:
  1、dom深度尽量浅。
  2、减少inline javascript、css的数量。
  3、使用现代合法的css属性。
  4、不要为id选择器指定类名或是标签,因为id可以唯一确定一个元素。
  5、避免后代选择符,尽量使用子选择符。原因:子元素匹配符的概率要大于后代元素匹配符。后代选择符;#tp p{}子选择符:#tp>p{}`
  6、避免使用通配符,举一个例子,.mod .hd *{font-size:14px;}根据匹配顺序,将首先匹配通配符,也就是说先匹配出通配符,然后匹配.hd(就是要对dom树上的所有节点进行遍历他的父级元素),然后匹配.mod,这样的性能耗费可想而知.

关于Script标签位置

Javascript的加载和执行的特点:
  (1)载入后马上执行;
  (2)执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)。原因:因为浏览器需要一个稳定的DOM树结构,而JS中很有可能有 代码直接改变了DOM树结构,比如使用 document.write 或 appendChild,甚至是直接使用的location.href进行跳转,浏览器为了防止出现JS修 改DOM树,需要重新构建DOM树的情况,所以 就会阻塞其他的下载和呈现。

减少JavaScript对性能的影响

  (1)将所有的script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
  (2)尽可能地合并脚本。页面中的script标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。
  (3)采用无阻塞下载 JavaScript 脚本的方法:
    (1)使用script标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本);
    (2)使用动态创建的script元素来下载并执行代码;