前言
根据 W3C 的规范,我们平常会将 HTML、CSS、JS 进行分层书写;并且一般 CSS 在上方,HTML 随后书写,JS 在最后才会书写。相信这是读者书写前端的默认习惯了,但是你是否有想过为啥要如此进行排列呢?
请记住下面的结论,接下来我们慢慢分析:
- CSS:CSS 不会阻止 DOM 树解析,但是会阻止 DOM 树渲染。
- JS:JS 会阻止 DOM 树解析以及渲染。
CSS 为何会在最上方显示?
CSS 加载不会阻塞 DOM树 的解析,但是 CSS 加载会阻塞 DOM树 的渲染,CSS 加载会阻塞后面 JS 语句的执行。
个人猜测:CSS是有可能会修改 DOM 的(别忘了 context),所以 CSS 加载会阻塞 DOM树 的渲染。CSS 放在最上方,也是防止用户可能会看见默认的 HTML 样式,造成用户体验不佳。
JS 为何会在最下方显示?
JS 会阻止 DOM 树解析以及渲染(因为 JS 可以操作 DOM)。一般来说,JS 都远比 CSS 和 HTML 要来的庞大。若是等 JS 完全加载完毕后,才显示 HTML 那么用户白屏的时间会稍长(参考 SPA 开发所出现的白屏时间)。
但是 script 加载分为几种情况:
- 默认只会执行完了当前脚本,才会继续渲染页面或者往后执行其他脚本。
- 使用 defer 属性可以让脚本先下载,等页面加载完毕之后再执行,一般是按照顺序来执行。
- 使用 async 属性可以让脚本一边下载,一边让页面渲染,等脚本下载好了,就先停止页面渲染,再执行脚本,可能不会按照顺序来执行。
关于重排以及重绘
重排
- 重排(回流、reflow):JS 动态的修改 DOM 即更改了DOM树了,更改 DOM 树之后,renderTree也就变了(可以理解为 DOM 树处理之后,会进行 renderTree 处理),renderTree变了也就是要重新建立一个renderTree了 ,这个过程叫做重排。故:重排必定引发重绘。
重排也叫做回流,网上看到一个解释比较有趣:回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流。 只要满足其中一个条件便会触发:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变(位置、边距、边框、宽度和高度等发生改变)
- 元素内容变化(文字或图片内容等发生改变)
- 元素字体大小变化(因为会使布局改变)
- 添加或者删除可见的DOM元素或者 display 触发
- 设置 offsetWidth 和 offsetHeight 等属性
- 获取 offsetWidth 和 offsetHeight 等属性(渲染队列不为空时,这一块后续进行说明)
设置宽高属性,触发重排很好理解,这是修改了元素的大小。获取这些属性会触发重排,是因为浏览器的渲染队列机制。当我们修改了元素的几何属性,导致浏览器触发重排或重绘时,它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作,当我们去获取这些数据时,浏览器为保证我们获取的数据是最新的,就会将渲染队列清空。
注意:如果把一个 DOM 的宽高信息确定,然后在 DOM 内部触发重排,就只会触发局部重排,否则会进行全局重排。
重绘
- 重绘(repaint):DOM 节点的 CSS 样式颜色、字体等不改变布局的变化所引发的重新渲染过程叫做重绘。改变的是cssTree 一部分变化,对randerTree影响相对较小,所以相对与重排而言对浏览器性能影响较小。
但是 table 及其内部元素比较特殊,它可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,所以尽量避免使用table布局。 只要满足下面条件便会触发:
- 当页面中元素样式的改变且并不影响它在文档流中的位置时(例如:color、background-color、visibility等)
visibility只会触发重绘是因为 visibility 为 hidden 的元素虽然不可见,但是位置不会消失。
- 当页面中元素样式的改变且并不影响它在文档流中的位置时(例如:color、background-color、visibility等)
浏览器的渲染队列
当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。当我们去获取这些数据时,浏览器为保证我们获取的数据是最新的,就会将渲染队列清空。
优化建议
- 避免频繁的样式操作,最好一次性重写style(cssText),或者一次性更改class,避免频繁操作DOM。
- 需要多次重排的元素(比如复杂动画的元素)使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁重排。
- 动画尽量使用 transition、transform (这样使用的 GPU 加速)
- 能使用 CSS 完成的事情,请不要交给 JavaScript
由于 DOM 和 JavaScript 是被分开独立实现的,因此,每一次在通过 JS 操作 DOM 的时候,就需要先去连接 JS 和 DOM。