Skip to content

解析流程

参考

当浏览器从上到下解析整个HTML文档时,

如果遇见外部URL资源,就会发送请求加载对应文件(现代浏览器可能会同时发送多个请求加载文件)。

如果遇见内联的样式表,就会立即解析(但不一定会立即渲染出样式);如果遇见内联的脚本,就会立即解析和执行;

样式表阻塞

当HTML解析器遇见一个style标签时,会按顺序解析里面的样式;当HTML解析器遇见一个link标签,会发送一个外部样式表的请求。这样的后果是到导致后面的后面的JS代码

  • 如果是内联脚本,则必须等待前面的样式表加载和解析完成才会执行
  • 如果是外部脚本,浏览器会发送下载外部脚本文件的请求(CSS文件和JS文件可能同步下载),即使文件已经返回,也必须等待前面的样式表加载和解析完成

这是因为JS执行依赖最新的CSS渲染(或者说最精确的样式信息)。

脚本阻塞

如果遇见普通的script(无async或defer)时,不论他是内联脚本还是外部脚本,都会阻塞浏览器进一步解析HTML文档(即暂时无法处理这个脚本后面其他需要加载的URL),而必须等待该标签代表的脚本文件执行完毕(如果是外联的脚本,甚至需要等到这个js文件加载成功并执行完毕。

这是因为:JS可能影响后续的文档,可能向文档流中插入信息,也可能改变后续script脚本的全局变量,因此浏览器干脆在解析和执行script标签的时候,阻塞后续文档的解析

整理上面可以得到下面结论

  • CSS文档的加载和解析,阻塞的是脚本的执行而不是脚本的加载。
  • 同步JS脚本的加载解析和执行,是会影响HTML解析器的工作,导致后面的所有资源都无法被加载。

DOMContentLoaded与load

当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等位于脚本前面的css加载完才能执行。在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。

页面上所有的资源(图片,音频,视频等)被加载以后才会触发load事件,简单来说,页面的load事件会在DOMContentLoaded被触发之后才触发。

参考

script标签上的asyncdefer的区别,参考Script - MDN

script标签上的asyncdefer属性,决定了脚本加载时采用同步方式还是异步方式。

  • 如果不加上这两个属性,默认为同步加载脚本,加载和执行时会阻塞页面的渲染,即浏览器按顺序解析DOM树及脚本,遇见脚本会阻塞DOM树生成并执行脚本。
  • async 和 defer 方式是用异步的方式加载脚本,不会阻塞页面渲染,它们之间的不同在于何时执,
    • async 方式是加载后马上执行,
    • defer 方式是加载后等所有 DOM 都渲染好触发 DOMContentLoaded 事件之前执行,
    • 所以 async 方式里面的脚本都是乱序执行,defer 方式加载的代码都是按序执行的,按序执行对有依赖的代码非常重要。
    • 若两个属性同在,会忽略defer而遵从async

通过动态生成script的方法,可以实现JSONP等功能

渲染流程

参考:

下面展示了大概的渲染过程

  • 浏览器首先解析三个东西:
    • HTML/SVG/XHTML,产生一个DOM Tree。
    • CSS,产生CSS Rule Tree。
    • Javascript,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree.
  • 解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。注意:
    • Rendering Tree 渲染树并不等同于DOM树,因为一些像Head或display:none的东西就没必要放在渲染树中了。
    • CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个DOM结点。
    • 然后计算DOM结点的位置,这又叫layout和reflow过程。
  • 最后通过调用操作系统Native GUI的API绘制。

Repaint重绘

Repaint表示页面上某个元素的的非定位样式需要重新绘制(比如利用JS改变了元素节点的背景颜色,此时不需要重新布局)

Reflow回流

Reflow表示元素节点的几何尺寸发生了变化,此时需要重新计算并构建渲染树(即此时需要重新布局),成本比Repaint的成本高得多的多

下面是几条减少Repaint和Reflow的原则

  • 不要一条一条地修改DOM的样式,如果修改的样式过多可以将样式统一在某个类中,然后直接更改元素节点的className;
  • 使用临时变量保存DOM节点,而不是每次都直接对DOM节点进行操作(减少元素节点的读写),在JS性能与浏览器性能方面都能得到一些优化;
  • 尽可能修改层级比较低的DOM,缩小操作的影响范围;
  • 放弃使用table进行布局,因为一个很小的改动都会造成整个table的重新布局

在每一帧中完成的工作

参考:看了就会的浏览器帧原理

可以看见requestAnimationFrame是在帧开始的时机执行的,而requestIdleCallback是在帧末尾的空闲时间执行的

使用Chrome调试工具查看渲染流程

参考