前端分享会--重排、重绘、阻塞
《前端分享会》将是本坑新开的专题,这个专题主要是平时本坑在单位分享会分享的,和平时看到想分享的内容。涉及的内容可以是比较简单,初级。当然也会有程度比较高知识分享。
可能看官觉得,太基础和太简单的内容有什么好分享的。非也!很多时候,可能理所当然,然而当面试问这些问题时,你会发现这些简单知识点距离如此之近而你却打不出其中原委。实践证明,很多基础知识点蕴藏着打开新世界的钥匙。理解之,能解开你在一系列建立在该基础上的技术实现给你带来的疑问。
即使,不是这样,多多回顾甚至背诵这些基础知识点的来龙去脉,可以轻松应对面试题的绝大部分。
今天来聊聊重排和重绘。重排在一些文章中也叫回流。
重排和重绘
从字面意思来看重排就是重新排列,而重绘就是重新绘制。很显然既然是重新排列,自然就要重新绘。重排和重绘都是是根据dom
元素来说的?其实并不完全是。而是render tree
的节点。说到这里,我们要回顾下浏览器渲染一个页面的过程是怎么样的。
将HTML
构建成一个DOM
树(DOM = Document Object Model
文档对象模型),DOM
树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
将CSS
解析成CSS
去构造CSSOM
树( CSSOM
= CSS Object Model CSS
对象模型)
根据DOM
树和CSSOM
来构造 Rendering Tree
(渲染树)。注意:Rendering Tree
渲染树并不等同于 DOM
树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。
有了Render Tree
,浏览器已经能知道网页中有哪些节点、各个节点的CSS
定义以及他们的从属关系。
再来就是Layout
,顾名思义就是计算出每个节点在屏幕中的位置 layout render tree
。
而后就是绘制,即遍历render
树,并使用浏览器UI
后端层绘制每个节点。
在整个过程中,javascript
可能会改变dom
树和cssom
的结构,所以浏览器往往会等待js的加载,因此搁置整个渲染数的构建。
不同浏览器对布局和绘制的顺序可以是不同的,根据不同的浏览器优化策略,浏览器能够更好的协调布局和绘制过程。
而我们说重绘和重排,就是重新去布局的过程和重新绘制的过程。
渲染树的变更会导致重排,而渲染数的节点的属性变化回导致重绘。
阻塞
从上面的过程,我们要特别要知道一点:dom
节点是边加载边渲染的。同时css资源和js资源也是同时在加载,文档边渲染的。
由此就会带来一些阻塞问题,也就是用户看到白屏。比如:
如果dom
节点中涉及到资源加载会影响到dom
的渲染。像js
加载和执行会影响dom
树的解析。 css
加载会影响到cssom
的形成,但是不影响dom
树的形成。所以js资源我们会放到文档末尾的原因,而样式是放在前面。
不管是js资源也好还是css
资源也好,我们都要求尽量压缩和小,也是同一个道理。
总结一下,DOM
树的构建会被阻塞:
HTML
的响应流被阻塞在了网络中
有未加载完的脚本
遇到了script
节点,但是此时还有未加载完的样式文件,dom
解析不受到影响
下面有个例子(来自网络,地址在文章底部
)
<html>
<body>
<link rel="stylesheet" href="example.css">
<div>Hi there!</div>
<script>
document.write('<script src="other.js"></scr' + 'ipt>');
</script>
<div>Hi again!</div>
<script src="last.js"></script>
</body>
</html>
<html>
<body>
<link rel="stylesheet" href="example.css">
<div>Hi there!</div>
<script>...
首先,解析器遇到了example.css
,并将它从网络中下载下来。下载样式表的过程是耗时的,但是解析器并没有被阻塞,继续往下解析。接下来,解析器遇到script
标签,但是由于样式文件没有加载下来,阻塞了该脚本的执行。解析器被阻塞住,不能继续往下解析。
渲染树也会被样式文件阻塞,所以这时候没有浏览器不会去渲染页面,换句话说,如果example.css
文件下载不下来,Hi there!
是显示不出来的。
<html>
<body>
<link rel="stylesheet" href="example.css">
<div>Hi there!</div>
<script>
document.write('<script src="other.js"></scr' + 'ipt>');
</script>
一旦example.css
文件加载完成,渲染树也就被构建好了。
内联的脚本执行完之后,解析器就会立即被other.js
阻塞住。一旦解析器被阻塞,浏览器就会收到绘制请求,"Hi there!"
也就显示在了页面上。
当other.js
加载完成之后,解析器继续向下解析。。。
<html>
<body>
<link rel="stylesheet" href="example.css">
<div>Hi there!</div>
<script>
document.write('<script src="other.js"></scr' + 'ipt>');
</script>
<div>Hi again!</div>
<script src="last.js"></script>
解析器遇到last.js
之后会被阻塞,然后浏览器收到了另一个绘制请求,"Hi again!"
就显示在了页面上。最后last.js会被加载,并且会被执行。