浏览器渲染页面的一般过程:
1.浏览器解析html源码,然后创建一个 DOM树。
在DOM树中,每一个HTML标签都有一个对应的节点,并且每一个文本也都会有一个对应的文本节点。DOM树的根节点就是 documentElement,对应的是html标签。
2.浏览器解析CSS代码,计算出最终的样式数据。构建CSSOM树。
对CSS代码中非法的语法它会直接忽略掉。解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。
3.DOM Tree + CSSOM --> 渲染树(rendering tree)。
渲染树和DOM树有点像,但是是有区别的。DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。而且一大段文本中的每一个行在渲染树中都是独立的一个节点。渲染树中的每一个节点都存储有对应的css属性。
4.一旦渲染树创建好了,浏览器就可以根据渲染树直接把页面绘制到屏幕上。
以上四个步骤并不是一次性顺序完成的。如果DOM或者CSSOM被修改,以上过程会被重复执行。实际上,CSS和JavaScript往往会多次修改DOM或者CSSOM。
阻塞渲染:CSS与JavaScript
浏览器其实主要由两个部分组成:JS引擎和渲染引擎。渲染引擎会先加载页面,逐步完成上述过程。
但在遇到link
和script
标签时,HTML Paser会被阻塞。
现代浏览器在HTML解析器被阻塞时,仍然会识别后面的资源,但这属于预加载,并不是真正意义上的解析。
有以下两点:
1.默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕
2.JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性
存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。另外:
- 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
- JavaScript 可以查询和修改 DOM 与 CSSOM。
- CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。
所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:
- CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
- JavaScript 应尽量少影响 DOM 的构建。
解析器一旦读取到script标签(内联或外联)时,会阻塞DOM构建,它会去请求脚本并执行完成后才继续执行下去。
这里async和defer标签除外,async和defer属性会让脚本延后执行,不会阻塞浏览器解析。
重绘和重排(repaints and reflows)
每个页面至少在初始化的时候会有一次重排操作。任何对渲染树的修改都有可能会导致下面两种操作:
1.重排就是渲染树的一部分必须要更新 并且节点的尺寸发生了变化。这就会触发重排操作。
2.重绘部分节点需要更新,但是没有改变他的集合形状,比如改变了背景颜色,这就会触发重绘。什么情况下会触发重绘或重排
记住,重排必然会引起重绘
下面任何操作都会触发重绘或者重排:
- 增加或删除DOM节点设置 display: none;(重排并重绘) 或visibility: hidden(只有重排)
- 移动页面中的元素
- 增加或者修改样式用户
- 改变窗口大小
- 滚动页面等
减少重绘和重排
1.不要一个一个地单独修改属性,最好通过一个classname来定义这些修改
// bad
var left = 10, top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// good
el.className += " theclassname";
2.把对节点的大量修改操作放在页面之外用 documentFragment来做修改clone 节点,在clone之后的节点中做修改,然后直接替换掉以前的节点通过 display: none 来隐藏节点(直接导致一次重排和重绘),做大量的修改,然后显示节点(又一次重排和重绘),总共只会有两次重排。
3.不要频繁获取计算后的样式。如果你需要使用计算后的样式,最好暂存起来而不是直接从DOM上读取。
4.总的来说,总是考虑到渲染树的存在,考虑到你的一次修改会导致多大的绘制操作。比如绝对定位元素的动画就不会影响其他大部分元素。
document.createElement
使用 document.createElement 创建的 script 默认是异步的,示例如下。
console.log(document.createElement("script").async); // true
所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。
如果使用 document.createElement 创建 link 标签会怎样呢?
const style = document.createElement("link");
style.rel = "stylesheet";
style.href = "index.css";
document.head.appendChild(style); // 阻塞?
以上代码不同浏览器会有所不同,已知Chrome中不会阻塞渲染