大家好,我是桃翁, 这是我 101 篇原创文章。
最近在面试的时候经常会问:如何理解重排和重绘?
我发现很多候选人都没有答道关键点上,感觉是在哪里看到过相关的文章,听起来零零散散,毫无逻辑。
错误示范
一般的面试过程就是这样的:
面试官:如何理解重排和重绘?
候选人:重排就是当页面的结构发生变化了,就会重排,比如改变变字体的大小,增删 DOM 元素这样的。重绘就是页面结构没有变化,只是外观变了,比如改了一下字体颜色、背景颜色这样的。就只会发生重绘。
“当然他说的也没错,我也不能直接说他错,就继续引导
”
面试官:那重排和重绘有什么关系吗?
候选人:重排一定会导致重绘,重绘不一定会导致重排。
面试官:为什么呢?
候选人:因为重排结构发生变化了嘛,肯定会导致重绘。
我这时候表情就是这样:
“如果你觉得上面的回答很真实,那下面的你确定得好好看看。
”
接下来一般我不会直接跳过,我会再问一下浏览器关键渲染路径引导一下。
如果不知道的话,我会再引导一下(这个时候其实基本已经放弃了)。
问一下你知道当浏览器加载到一个 HTML 会发生什么事情吗?
如果还是不知道的话,这下一题了。
如果知道关键渲染路径的,基本引导一下还是可以搞明白,如果不清楚的,肯定是理解不了重排和重绘的。
考点
这道题我一般考察两个点:
浏览器的关键渲染路径。如果答不到这上面,一般这个题就凉了。
性能优化,如果减少重绘和回流,当然这个点肯定也是要基于对 关键渲染路径 的理解(这点不是关键点)。
复习
“复习的目的是为了知道考点是啥,简单的给大家复习一下,更详细的内容希望按我介绍的知识点(可以看我文末推荐的文章进行深入学习),毕竟复习不是上课。
”
我们可以能知道,写了 HTML、CSS、JavaScript 就可以将页面渲染到屏幕上,但是浏览器是如何把我们的代码渲染到屏幕上的像素点的呢?这就需要了解到这么一个概念 CRP:
关键渲染路径(Critical Rendering Path)是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。
大致步骤是这样:在解析 HTML 时会创建 DOM,HTML 可以请求 JavaScript,而 JavaScript 反过来,又可以更改 DOM。HTML 包含或请求样式,依次来构建 CSSOM。
浏览器引擎将两者结合起来以创建 Render Tree (渲染树),Layout(布局)确定页面上所有内容的大小和位置,确定布局后,将像素 Paint (绘制)到屏幕上。
优化关键渲染路径可以缩短首次渲染的时间。了解和优化关键渲染路径对于确保重排和重绘可以每秒 60 帧的速度进行,以确保高效的用户交互并避免讨厌是很重要的。
接下来研究一下详细的过程:
步骤
1. 生成 DOM
DOM 构建是增量的。浏览器从远程下载 Byte
=> 根据相应的编码 (如 utf8) 转化为字符串
=> 通过 AST 解析为 Token
=> 生成 Node
=> 生成 DOM
。
单个 DOM 节点以 startTag token 开始,以 endTag token 结束。节点包含有关 HTML 元素的所有相关信息。该信息是使用 token 描述的。节点根据 token 层次结构连接到 DOM 树中。
如果另一组 startTag 和 endTag token 位于一组 startTag 和 endTag 之间,则在节点内有一个节点,这就是我们定义 DOM 树层次结构的方式。
2. 生成 CSSOM
浏览器解析 css 文件,生成 CSSOM。CSSOM 包含了页面所有的样式,也就是如何展示 DOM 的信息。
CSSOM 跟 DOM 很像,但是不同。
DOM 构造是增量的,CSSOM 却不是。CSS 是渲染阻塞的:浏览器会阻塞页面渲染直到它接收和执行了所有的 CSS。
CSS 是渲染阻塞是因为规则可以被覆盖,所以内容不能被渲染直到 CSSOM 的完成。
3. Render Tree
渲染树(Render Tree)包括了内容和样式:DOM 和 CSSOM 树结合为渲染树。
为了构造渲染树,浏览器检查每个节点,从 DOM 树的根节点开始,并且决定哪些 CSS 规则被添加。
渲染树只包含了可见内容(body 里的部分)。
Head(通常)不包含任何可见信息,因此不会被包含在渲染树种。如果有元素上有 display: none;
,它本身和其后代都不会出现在渲染树中。
4. Layout
一旦渲染树被构建,布局变成了可能。布局取决于屏幕的尺寸。布局这个步骤决定了在哪里和如何在页面上放置元素,决定了每个元素的宽和高,以及他们之间的相关性。
“提示:一个页面渲染在不同尺寸的屏幕上,比如渲染在移动端和 PC 端上,展示有差异,在前面的步骤都是不变的,只有在布局的时候才会根据屏幕尺寸进行差异化处理。
”
5. Paint
最后一步是将像素绘制在屏幕上,栅格化所有元素,将元素转换为实际像素。
一旦渲染树创建并且布局完成,像素就可以被绘制在屏幕上。加载时,整个屏幕被绘制出来。之后,只有受影响的屏幕区域会被重绘,浏览器被优化为只重绘需要绘制的最小区域。
绘制时间取决于何种类型的更新被附加在渲染树上。绘制是一个非常快的过程,所以聚焦在提升性能时这大概不是最有效的部分
重排(Reflow)和重绘(Repaint)
了解完上面的关键路径渲染之后,再来了解重排和重绘简直就是小 case。
重排(Reflow):元素的 位置发生变动 时发生重排,也叫回流。此时在 Layout 阶段,计算每一个元素在设备视口内的确切位置和大小。当一个元素位置发生变化时,其父元素及其后边的元素位置都可能发生变化,代价极高。
“
在回答什么是重排的时候,关键不是位置发生变动,这只是原因(Why),而不是 What。What 是重新计算每个元素在设备视口内的确切位置和大小。
”重绘(Repaint): 元素的 样式发生变动 ,但是位置没有改变。此时在关键渲染路径中的 Paint 阶段,将渲染树中的每个节点转换成屏幕上的实际像素,这一步通常称为绘制或栅格化。
“
而回答什么是重绘的关键点在于在关键渲染路径中的 Paint 阶段,将渲染树中的每个节点转换成屏幕上的实际像素,这才是 What。
”
JavaScript 与关键路径渲染
前面聊步骤的时候基本都是聊的 HTML 、CSS 与 CRP 的关系,最后再聊一下 JS 与 CRP 的关系,再看一下文章开头的这个图。
JavaScript 的执行是在生成渲染树之前的。这也是为什么 JavaScript 的加载、解析与执行会阻塞 DOM 的构建,阻塞页面的渲染。
这其实是非常合理的
因为 JavaScript 可以修改网页的内容,它可以更改 DOM,如果不阻塞,那么这边在构建 DOM,那边 JavaScript 在改 DOM,如何保障最终得到的 DOM 是否正确?
而且在 JS 中前一秒获取到的 DOM 和后一秒获取到的 DOM 不一样是什么鬼?它会产生一系列问题,所以 JS 是阻塞的,它会阻塞 DOM 的构建流程,所以在 JS 中无法获取 JS 后面的元素,因为 DOM 还没构建到那。
这就是为什么我们需要把 js 放在页面底部的原因,尽量保证 DOM 树生成完毕再去加载 JS,从而出现这样的效果。
性能优化
基于以上的分析,简单的说几条性能优化的方式,自己可以去分析一下为什么这些方式可以做性能优化。
减少 DOM 树渲染时间(譬如降低 HTML 层级、标签尽量语义化等等)
减少 CSSOM 树渲染时间(降低选择器层级等等)
减少 HTTP 请求次数及请求大小
将 css 放在页面开始位置
将 js 放在页面底部位置,并尽可能用 defer 或者 async 避免阻塞的 js 加载,确保 DOM 树生成完才会去加载 JS
用 link 替代@import
如果页面 css 较少,尽量使用内嵌式
为了减少白屏时间,页面加载时先快速生成一个 DOM 树
正确的性能优化思路
再啰嗦一下性能优化相关的,当你遇到一个性能问题的时候,绝对不是去网上找一些性能优化的方法,然后不管三七二十一,就整上去,这样大概率是没啥用的。
第一件事情,一定是要先分析性能的瓶颈在哪里。
第一件事情,一定是要先分析性能的瓶颈在哪里。
第一件事情,一定是要先分析性能的瓶颈在哪里。
比如你遇到了首屏加载的性能问题,你就要根据开发者工具,比如看 network 是否是由于资源体积太大导致请求慢,还是后端处理慢,还是资源太多了加载慢.
如果这些都不是,可能是因为 渲染慢,再去分析 performce 面板,看一下是 js 执行慢,还是啥原因。
再比如你遇到了 webpack 的性能问题,比如打包的资源太大了,你要去解决这个问题,你也不应该直接去随便找几个优化的方法就开始整。
而是先应该通过 webpack-bundle-analyzer 插件去分析包大的原因是什么?
是依赖包太大了,没有做按需加载?
还是重复的打包了几个版本的相同依赖?
还是因为 src 太大了,是否需要做个动态加载?
还是因为其他的,通过 webpack-bundle-analyzer 分析出来的组成内容去找问题。
最后再总结一下,遇到问题应该先通过各种分析工具,找到出现性能瓶颈的原因,再根据这个原因去寻找对应的优化方式,要对症下药。
不管是面试的时候回答,还是自己平时在处理问题的时候都要这样,只有分析出问题了,解决方案一大堆,找不到问题,瞎搞就是浪费时间。
参考回答
我相信复习完之后,对这个知识点应该是清楚了,面试的时候不需要说这么多,把关键点说出来,让面试官知道你是懂的就行,如果面试官有兴趣的话会继续追问的,这个时候再详细的跟他介绍追问的点。
如果是我被问到这个题,我的回答大概是这样的,仅供参考:
“重排和重绘是浏览器关键渲染路径上的两个节点, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局(也叫 layout)步骤来确定页面上所有内容的大小和位置,确定布局后,将像素 绘制 (也叫 Paint)到屏幕上。
其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。
如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排。
”
上面这样回答,我觉得在绝大部分面试官那里已经可以拿到这个题的分了。
不过面试官还是有可能继续往深的问,小伙伴们可以在评论区说一说你们还遇到过哪些相关的问题,我后面再继续帮助大家一起分析。
对于性能问题上,减少重绘和回流感觉没有那么重要,因为优化一般情况不是很明显,不答问题也不大,更多的性能优化是在整个链路上的优化,比如性能优化标题里面的那 8 个点。
最后
在行业不景气的时候,希望大家都能顺利找到合适的工作。
关于关键路径渲染和重排、重绘,我最后还是忍不住给大家推荐一下大漠老师的两篇文章,有空去拜读一下子。
关键渲染路径(CRP)[1]
理解 Web 的重排和重绘[2]
参考文章:
关键渲染路径[3]
developers.google.cn:关键渲染路径[4]
参考资料
[1]
关键渲染路径(CRP): https://www.w3cplus.com/performance/repaint-and-reflow.html
[2]理解 Web 的重排和重绘: https://www.w3cplus.com/performance/repaint-and-reflow.html
[3]关键渲染路径: https://github.com/berwin/Blog/issues/29
[4]developers.google.cn:关键渲染路径: https://developers.google.cn/web/fundamentals/performance/critical-rendering-path?hl=zh-cn
“如果你看完文章之后有任何想法,欢迎在留言区交流,如果你觉得文章帮助到了你,欢迎关注加三连(点赞、在看、分享),你对笔者的每次支持,都是笔者前进的动力。
”
“如果你想加入前端交流群,或者想与笔者进行其他交流,可以加我个人微信:1076629390
”