【面试系列一】如何回答如何理解重排和重绘

2022/03/15 22:14:22

最近在面试的时候经常会问:如何理解重排和重绘?

我发现很多候选人都没有答道关键点上,感觉是在哪里看到过相关的文章,听起来零零散散,毫无逻辑。

错误示范

一般的面试过程就是这样的:

面试官:如何理解重排和重绘?

候选人:重排就是当页面的结构发生变化了,就会重排,比如改变变字体的大小,增删 DOM 元素这样的。重绘就是页面结构没有变化,只是外观变了,比如改了一下字体颜色、背景颜色这样的。就只会发生重绘。

当然他说的也没错,我也不能直接说他错,就继续引导

面试官:那重排和重绘有什么关系吗?

候选人:重排一定会导致重绘,重绘不一定会导致重排。

面试官:为什么呢?

候选人:因为重排结构发生变化了嘛,肯定会导致重绘。

我这时候表情就是这样:

image.png

如果你觉得上面的回答很真实,那下面的你确定得好好看看。

接下来一般我不会直接跳过,我会再问一下浏览器关键渲染路径引导一下。

如果不知道的话,我会再引导一下(这个时候其实基本已经放弃了)。

image.png

问一下你知道当浏览器加载到一个 HTML 会发生什么事情吗?

如果还是不知道的话,这下一题了。

如果知道关键渲染路径的,基本引导一下还是可以搞明白,如果不清楚的,肯定是理解不了重排和重绘的。

考点

这道题我一般考察两个点:

  1. 浏览器的关键渲染路径。如果答不到这上面,一般这个题就凉了。
  2. 性能优化,如果减少重绘和回流,当然这个点肯定也是要基于对 关键渲染路径 的理解(这点不是关键点)。

复习

复习的目的是为了知道考点是啥,简单的给大家复习一下,更详细的内容希望按我介绍的知识点(可以看我文末推荐的文章进行深入学习),毕竟复习不是上课。

我们可以能知道,写了 HTML、CSS、JavaScript 就可以将页面渲染到屏幕上,但是浏览器是如何把我们的代码渲染到屏幕上的像素点的呢?这就需要了解到这么一个概念 CRP:

关键渲染路径(Critical Rendering Path)是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。

大致步骤是这样:在解析 HTML 时会创建 DOM,HTML 可以请求 JavaScript,而 JavaScript 反过来,又可以更改 DOM。HTML 包含或请求样式,依次来构建 CSSOM。浏览器引擎将两者结合起来以创建 Render Tree (渲染树),Layout(布局)确定页面上所有内容的大小和位置,确定布局后,将像素 Paint (绘制)到屏幕上。

image.png

优化关键渲染路径可以缩短首次渲染的时间。了解和优化关键渲染路径对于确保重排重绘可以每秒 60 帧的速度进行,以确保高效的用户交互并避免讨厌是很重要的。

image.png

接下来研究一下详细的过程:

步骤

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 的关系,再看一下文章开头的这个图。

image.png

JavaScript 的执行是在生成渲染树之前的。这也是为什么 JavaScript的加载、解析与执行会阻塞 DOM 的构建,阻塞页面的渲染。

这其实是非常合理的

因为JavaScript 可以修改网页的内容,它可以更改 DOM,如果不阻塞,那么这边在构建DOM,那边 JavaScript 在改 DOM,如何保障最终得到的DOM是否正确?而且在JS中前一秒获取到的DOM和后一秒获取到的DOM不一样是什么鬼?它会产生一系列问题,所以JS是阻塞的,它会阻塞DOM的构建流程,所以在 JS 中无法获取 JS 后面的元素,因为 DOM 还没构建到那。

这就是为什么我们需要把 js 放在页面底部的原因,尽量保证 DOM 树生成完毕再去加载 JS,从而出现这样的效果。

image.png

性能优化

基于以上的分析,简单的说几条性能优化的方式,自己可以去分析一下为什么这些方式可以做性能优化。

  1. 减少DOM树渲染时间(譬如降低HTML层级、标签尽量语义化等等)
  2. 减少CSSOM树渲染时间(降低选择器层级等等)
  3. 减少 HTTP 请求次数及请求大小
  4. 将 css 放在页面开始位置
  5. 将 js 放在页面底部位置,并尽可能用 defer 或者 async 避免阻塞的 js 加载,确保 DOM 树生成完才会去加载 JS
  6. 用 link 替代@import
  7. 如果页面css较少,尽量使用内嵌式
  8. 为了减少白屏时间,页面加载时先快速生成一个DOM树

正确的性能优化思路

再啰嗦一下性能优化相关的,当你遇到一个性能问题的时候,绝对不是去网上找一些性能优化的方法,然后不管三七二十一,就整上去,这样大概率是没啥用的。

第一件事情,一定是要先分析性能的瓶颈在哪里。

第一件事情,一定是要先分析性能的瓶颈在哪里。

第一件事情,一定是要先分析性能的瓶颈在哪里。

比如你遇到了首屏加载的性能问题,你就要根据开发者工具,比如看 network 是否是由于资源体积太大导致请求慢,还是后端处理慢,还是资源太多了加载慢,如果这些都不是,可能是因为 渲染慢,再去分析 performce 面板,看一下是 js 执行慢,还是啥原因。

再比如你遇到了 webpack 的性能问题,比如打包的资源太大了,你要去解决这个问题,你也不应该直接去随便找几个优化的方法就开始整,而是先应该通过 webpack-bundle-analyzer 插件去分析包大的原因是什么?

  • 是依赖包太大了,没有做按需加载?
  • 还是重复的打包了几个版本的相同依赖?
  • 还是因为 src 太大了,是否需要做个动态加载?
  • 还是因为其他的,通过 webpack-bundle-analyzer 分析出来的组成内容去找问题。

最后再总结一下,遇到问题应该先通过各种分析工具,找到出现性能瓶颈的原因,再根据这个原因去寻找对应的优化方式,要对症下药。

不管是面试的时候回答,还是自己平时在处理问题的时候都要这样,只有分析出问题了,解决方案一大堆,找不到问题,瞎搞就是浪费时间。

参考回答

我相信复习完之后,对这个知识点应该是清楚了,面试的时候不需要说这么多,把关键点说出来,让面试官知道你是懂的就行,如果面试官有兴趣的话会继续追问的,这个时候再详细的跟他介绍追问的点。

如果是我被问到这个题,我的回答大概是这样的,仅供参考:

重排和重绘是浏览器关键渲染路径上的两个节点, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局(也叫 layout)步骤来确定页面上所有内容的大小和位置,确定布局后,将像素 绘制 (也叫 Paint)到屏幕上。

其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。

如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排。

上面这样回答,我觉得在绝大部分面试官那里已经可以拿到这个题的分了。

不过面试官还是有可能继续往深的问,小伙伴们可以在评论区说一说你们还遇到过哪些相关的问题,我后面再继续帮助大家一起分析。

对于性能问题上,减少重绘和回流感觉没有那么重要,因为优化一般情况不是很明显,不答问题也不大,更多的性能优化是在整个链路上的优化,比如性能优化标题里面的那 8 个点。

最后

在行业不景气的时候,希望大家都能顺利找到合适的工作。

关于关键路径渲染和重排、重绘,我最后还是忍不住给大家推荐一下大漠老师的两篇文章,有空去拜读一下子。

参考文章:


Profile picture

桃翁 所写,你可以在 GitHub 上找到我