前言, 最近利用碎片时间拜读了一下尼古拉斯的另一巨作《高性能 JavaScript》, 今天写的文章从 "老生常谈" 的页面重绘和重排入手, 去探究这两个概念在页面性能提升上的作用.
一. 重排 & 重绘
有经验的大佬对这个概念一定不会陌生,"浏览器输入 URL 发生了什么". 估计大家已经烂熟于心了, 从计算机网络到 JS 引擎, 一路飞奔到浏览器渲染引擎. 经验越多就能理解的越深. 感兴趣的同学可以看一下这篇文章, 深度和广度俱佳 从输入 URL 到页面加载的过程? 如何由一道题完善自己的前端知识体系!
切回正题, 我们继续探讨何为重排. 浏览器下载完页面所有的资源后, 就要开始构建 DOM 树, 于此同时还会构建渲染树(Render Tree).(其实在构建渲染树之前, 和 DOM 树同期会构建 Style Tree.DOM 树与 Style Tree 合并为渲染树)
DOM 树
表示页面的结构
渲染树
表示页面的节点如何显示
一旦渲染树构建完成, 就要开始绘制 (paint) 页面元素了. 当 DOM 的变化引发了元素几何属性的变化, 比如改变元素的宽高, 元素的位置, 导致浏览器不得不重新计算元素的几何属性, 并重新构建渲染树, 这个过程称为 "重排". 完成重排后, 要将重新构建的渲染树渲染到屏幕上, 这个过程就是 "重绘". 简单的说, 重排负责元素的几何属性更新, 重绘负责元素的样式更新. 而且, 重排必然带来重绘, 但是重绘未必带来重排. 比如, 改变某个元素的背景, 这个就不涉及元素的几何属性, 所以只发生重排.
二. 重排触发机制
上面已经提到了, 重排发生的根本原理就是元素的几何属性发生了改变, 那么我们就从能够改变元素几何属性的角度入手
添加或删除可见的 DOM 元素
元素位置改变
元素本身的尺寸发生改变
内容改变
页面渲染器初始化
浏览器窗口大小发生改变
三. 如何进行性能优化
重绘和重排的开销是非常昂贵的, 如果我们不停的在改变页面的布局, 就会造成浏览器耗费大量的开销在进行页面的计算, 这样的话, 我们页面在用户使用起来, 就会出现明显的卡顿. 现在的浏览器其实已经对重排进行了优化, 比如如下代码:
- var div = document.querySelector('.div');
- div.style.width = '200px';
- div.style.background = 'red';
- div.style.height = '300px';
比较久远的浏览器, 这段代码会触发页面 2 次重排, 在分别设置宽高的时候, 触发 2 次, 当代的浏览器对此进行了优化, 这种思路类似于现在流行的 MVVM 框架使用的虚拟 DOM, 对改变的 DOM 节点进行依赖收集, 确认没有改变的节点, 就进行一次更新. 但是浏览器针对重排的优化虽然思路和虚拟 DOM 接近, 但是还是有本质的区别. 大多数浏览器通过队列化修改并批量执行来优化重排过程. 也就是说上面那段代码其实在现在的浏览器优化下, 只构成一次重排.
但是还是有一些特殊的元素几何属性会造成这种优化失效. 比如:
offsetTop, offsetLeft,...
scrollTop, scrollLeft, ...
clientTop, clientLeft, ...
getComputedStyle() (currentStyle in IE)
为什么造成优化失效呢? 仔细看这些属性, 都是需要实时回馈给用户的几何属性或者是布局属性, 当然不能再依靠浏览器的优化, 因此浏览器不得不立即执行渲染队列中的 "待处理变化", 并随之触发重排返回正确的值.
接下来深入的介绍几种性能优化的小 TIPS
3.1 最小化重绘和重排
既然重排 & 重绘是会影响页面的性能, 尤其是糟糕的 JS 代码更会将重排带来的性能问题放大. 既然如此, 我们首先想到的就是减少重排重绘.
3.1.1. 改变样式
考虑下面这个例子:
- // JavaScript
- var el = document.querySelector('.el');
- el.style.borderLeft = '1px';
- el.style.borderRight = '2px';
- el.style.padding = '5px';
这个例子其实和上面那个例子是一回事儿, 在最糟糕的情况下, 会触发浏览器三次重排. 然鹅更高效的方式就是合并所有的改变一次处理. 这样就只会修改 DOM 节点一次, 比如改为使用 CSSText 属性实现:
- var el = document.querySelector('.el');
- el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';
沿着这个思路, 聪明的老铁一定就说了, 你直接改个类名不也妥妥的. 没错, 还有一种减少重排的方法就是切换类名, 而不是使用内联样式的 cssText 方法. 使用切换类名就变成了这样:
- // CSS
- .active {
- padding: 5px;
- border-left: 1px;
- border-right: 2px;
- }
- // JavaScript
- var el = document.querySelector('.el');
- el.className = 'active';
3.1.2 批量修改 DOM
3.1.3 缓存布局信息
3.2 让元素脱离动画流
来源: https://segmentfault.com/a/1190000016990089