首页 > 其他分享 >什么是重排和重绘

什么是重排和重绘

时间:2022-10-31 10:31:09浏览次数:37  
标签:Dom 什么 元素 重排 var 重绘 appendToElement

当浏览器下载完页面所需元素(html标记,css层叠样式表,javascript,图片)之后,会生成两个东西:Dom树和渲染树。

Dom树

Dom树,主要是用来表示页面的Dom结构。

渲染树

渲染树主要是用来表示页面是如何进行渲染的。

Dom树中,除了隐藏节点,其余的节点需要与渲染树中的至少存在一个对应的节点。渲染树中的每一个节点,被称为帧或者是盒子。盒子具有内边距,外边距,边框,位置等属性。一旦渲染树构建完成之后,浏览器就开始进行绘制页面。

当Dom的变化影响到了元素的几何属性(宽和高等)——比如说修改了边框的宽度,或者是修改了高度,又或者给文章增加了内容导致元素的高度增加等,会引起浏览器进行重新计算元素的几何属性,同样,其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效。并重新构建渲染树,这个过程称为重排。完成重排之后,浏览器会重新绘制受影响的元素,这个过程被称为重绘。

并不是所有的Dom变化会影响元素的几何属性,例如,改变背景色,不会影响元素的几何属性,因此,这个时候是不会发生重排,仅仅会发生重绘,因为,元素的不布局没有发生变化。重排和重绘的代价都是昂贵的操作,他们会导致浏览器的UI线程卡顿,因此尽可能避免这类操作。

下面就是整个的基本流程图:

什么是重排和重绘_文档流

什么时候回发生重排

正如前面所说的,当页面的布局和几何属性发生改变的时候,就需要进行​​重排​​。以下的情况也同样会发生重排:

  • 添加和或者删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括:外边距,内边距,边框厚度,宽度,高度等属性发生改变)
  • 内容发生变化(例如:内容增加引起高度变化或者是图片被另外一个不同尺寸的图片所替换)
  • 页面渲染器进行初始化的
  • 浏览器窗口尺寸发生改变

根据改变的内容,渲染树中相对应的部分也需要进行计算。有些改变会触发整个页面的重排:例如,当滚动条出现的时候。

由于每次的重排都会产生计算消耗,大多数浏览器通过队列化修改并批量来优化 重排过程。但是,有些时候我们会强制进行刷新队列,并要求计划任务立刻执行。这些方法包括以下 方法:

  • offsetTop,offsetLeft,offsetWidth,offsetHeight
  • scrollTop,scrollLeft,scrollWidth,scrollHeight
  • clientTop,clientLeft,clientWidth,clientHeight
  • getComputedStyle()

以上的属性和方法需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并触发重排,以返回正确的值。因此,修改样式的过程中,最好避免使用以上的属性或者是方法。

如何优化重排效率

前面说到,重排和重绘的代价其实是非常昂贵的,因此,为了提高程序的响应速度,我们在平时的开发过程中应该尽量减少该操作的发生。为了减少重排或者是重绘的发生次数,我们可以有以下几点的操作。

合并对Dom的多次修改

var el = document.getElementById('mydiv');
el.style.width = '300px';
el.style.height = '400px';
el.style.margin = '15px';

在以上打代码中,我们能够看到对元素的几何属性发生了三次的修改,因此,上面的代码中会触发三次的重排和重绘。因此,我们可以将对元素的三次修改合并成一次修改,这样,就智慧触发一次重排重绘。修改后的代码如下:

var el = document.getElementById('mydiv');
el.style.cssText = 'width:30px;height:400px;margin:15px';

批量修改dom

当我们需要对Dom进行一系列操作时,可以通过以下步骤来减少重绘和重排:

  • 是元素脱离文档流
  • 对其应用多重改变
  • 把元素带回文档中

该过程会触发两次重排,第一步和第三步,如果忽略这两个步骤,那么第二步的修改就会触发多次的重排。这里要说的是,怎么才能使元素脱离文档流?主要的又以下几个方法:

  1. 隐藏元素,应用修改,重新显示
  2. 使用文档片段,在当前Dom之外构建一个子树,再把拷贝会文档
  3. 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素
  4. 使用虚拟Dom

接下来,就演示一下有关如何脱离文档流,来进行批量修改Dom的。我们先来构建一个场景:

<ul id="myList">
<li><a href="https://www.baidu.com">baidu</a></li>
<li><a href="https://www.qq.com">qq</a></li>
</ul>

上面是一个列表,假设我们要向上述列表中添加以下的数据:

var data = [
{

},
{
url: 'https://weibo.com/',
name: '新浪微博'
}
]

如果按照我们习惯性的思维,我们会这么去写:

function appendDataToElement (appendToElement, data) {
var a;
var li;
for (var i = 0; i <data.length; i++) {
a = document.createElement('a');
li = document.createElement('li');
a.href = data[i].url;
a.appendChild(document.createTextNode(data[i].name));
li.appendChild(a);
appendToElement.appendChild(li);
}
}

var appendToElement = document.getElementById('myList');
appendDataToElement(appendToElement, data);

这样写虽说是能够实现我们所想要的功能,但这样做的话,会在每次appendChild之后,引起浏览器的重排和重绘,如果数据量特别大的时候,就会发生很多次的重排和重绘,因此我们需要对上面的方法进行修改:

var appendToElement = document.getElementById('myList');
appendToElement.style.display = 'none';
appendDataToElement(appendToElement, data);
appendToElement.style.display = 'block';

这样话就只用渲染两次,这就是上面所说的隐藏元素,应用修改,重新显示

接下来实现一下后面的两种(除虚拟dom之外的方法)。

// 创建子树的方法
var appendToElement = document.createDocumentFragment();
appendDataToElement(appendToElement, data);
document.getElementById('myList').appendChild(appendToElement);

// 将原始元素拷贝到一个脱离文档的节点中
var old = document.getElementById('myList');
var clone = old.cloneNode();
appendDataToElement(appendToElement, data);
old.parentNode.replaceChild(clone, old);

针对于以上的方法,这边推荐使用构建子树的方法是创建子树的方法,因为他们所产生的的Dom遍历和重排次数最少,唯一潜在的问题就是文档片段未被充分利用。

还有一种就是虚拟Dom,有关虚拟Dom这个,其实可以参考Vue和React等比较现代的前端开发的内容。



标签:Dom,什么,元素,重排,var,重绘,appendToElement
From: https://blog.51cto.com/u_15516257/5808607

相关文章

  • 什么是回表?
    当需要查询的数据在索引树中不存在的时候,需要再次到聚集索引中去获取,这个过程叫做回表使用INNODB存储引擎的索引都维护了一个主键索引。product(id,name,price) id......
  • 什么是JAVA内存模型
    前言在并发编程中,当多个线程同时访问同一个共享的可变变量时,会产生不确定的结果,所以要编写线程安全的代码,其本质上是对这些可变的共享变量的访问操作进行管理。导致这种不......
  • 算法为什么难学,来了解一下
    (如何学习算法的)算法为什么难学很多人感叹算法怎么这么难学?一个原因就是算法本身就有一定的复杂性另一个原因可能就是讲的不到位,没有很好的理解。算法面临的困难是什......
  • 【面试题】 为什么说 bind 的实现非常考验对原型链的理解?
    前言bind的实现其实非常考验对原型链的理解。bind和apply,call是JS修改this指向的三把利器......
  • 为什么MySQL默认的隔离级别是RR而大厂使用的是RC?
    1写作目的现在的服务都是分布式,MySQL的集群架构也是一样。那么MySQL的集群架构中有一个点是读写分离,而读写分离是基于binlog实现的。那么接下来就MySQL的读写分离和binlog为......
  • JavaScript是什么?
    从头认识JavaScriptJavaScript简称‘JS’,是一款用来在网页中添加一些动态效果与交互功能的客户端语言,虽然如今看来这是前端工程师的必修课,但其实这在前后端还没有分离的......
  • 既然CPU有缓存一致性协议(MESI),为什么JMM还需要volatile关键字?
    ​​既然CPU有缓存一致性协议(MESI),为什么JMM还需要volatile关键字?​​​​MESI缓存一致性协议在哪里以及如何实现?​​​​Intel®64andIA-32ArchitecturesDeveloper’s......
  • CPU 的工作原理是什么?
    ​​CPU的工作原理是什么?​​​​你管这破玩意叫CPU?​​​​你管这破玩意叫编程语言?​​......
  • 为什么说 90% 的前端不会调试 Ant Design 源码?
    写react项目的小伙伴应该都用过antd组件库,但绝大多数同学并没有看过它的源码。而想深入掌握antd组件库,只熟悉参数是不行的,必须要深入到源码层面。所以今天就来分享下......
  • 数据结构 玩转数据结构 6-1 为什么要研究树结构
    0课程地址https://coding.imooc.com/lesson/207.html#mid=13454 1重点关注1.1为什么研究树结构高效    2课程内容3......