浅谈DOM的操作以及性能优化问题-重绘重排


Posted in Javascript onJanuary 08, 2017

写在前面:

大家都知道DOM的操作很昂贵。 

然后贵在什么地方呢?

一、访问DOM元素

二、修改DOM引起的重绘重排

一、访问DOM

像书上的比喻:把DOM和JavaScript(这里指ECMScript)各自想象为一个岛屿,它们之间用收费桥梁连接,ECMAScript每次访问DOM,都要途径这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高。因此,推荐的做法是尽量减少过桥的次数,努力待在ECMAScript岛上。我们不可能不用DOM的接口,那么,怎样才能提高程序的效率?

既然无法避免,那就减少访问。(width、offsetTop、left。。。能少就少,可以缓存起来的,就缓存)

// code1错误
console.time(1);
for(var i = 0; i < times; i++) {
 document.getElementById('div1').innerHTML += 'a';
}
console.timeEnd(1);
 
// code2正确
console.time(2);
var str = '';
for(var i = 0; i < times; i++) {
 str += 'a';
}
document.getElementById('div2').innerHTML = str;
console.timeEnd(2);
////////////////////////

浅谈DOM的操作以及性能优化问题-重绘重排

html集合&遍历DOM

html集合类似数组,但是跟数组还是不一样的。如: document.getElementsByTagName('a') 返回的html集合。这个集合是实时更新的,即后面代码修改了DOM,会反映在这个html集合里面。可尝试代码。

<body>
 <ul id='fruit'>
 <li> apple </li>
 <li> orange </li>
 <li> banana </li>
 </ul>
</body>
<script type="text/javascript">
 var lis = document.getElementsByTagName('li');
 var peach = document.createElement('li');
 peach.innerHTML = 'peach';
 document.getElementById('fruit').appendChild(peach);
 
 console.log(lis.length); // 4
</script>

正因为这个原因:html集合,读取 length 属性比数组消耗大多了。

要解决这个问题并不难,在遍历DOM集合的时候,缓存length就好了。不要每次使用就获取,主要体现在for循环中(你应该知道,for循环中,每一次都会执行判读语句,读取length)

console.time(0);
var lis0 = document.getElementsByTagName('li');
var str0 = '';
for(var i = 0; i < lis0.length; i++) {
 str0 += lis0[i].innerHTML;
}
console.timeEnd(0);
 
console.time(1);
var lis1 = document.getElementsByTagName('li');
var str1 = '';
for(var i = 0, len = lis1.length; i < len; i++) {
 str1 += lis1[i].innerHTML;
}
console.timeEnd(1);

二、重绘重排

1.什么是重绘重排?

浏览器下载完页面中的所有组件——HTML标记、JavaScript、CSS、图片之后会解析生成两个内部数据结构——DOM树和渲染树。

在文档初次加载时,浏览器引擎通过解析 html文档 构建一棵DOM树,之后根据DOM元素的几何属性构建一棵用于展示渲染的渲染树。渲染树中的节点被称为“帧”或“盒",符合CSS模型的定义,可理解为(包括理解页面元素为一个具有大小,填充,边距,边框和位置的盒子)。由于隐藏元素不需要显示,渲染树中并不包含DOM树中隐藏的元素(知道这点有用)。 当渲染树构建完成,浏览器把每一个元素放到正确的位置上,然后再根据每一个元素的其他样式,绘制页面。

由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。这也是为什么我们要避免使用table做布局的一个原因。

重绘:是一个元素外观的改变所触发的浏览器行为,例如改变visibility、outline、背景色等属性(上面说到的其他属性)。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。重绘不会带来重新布局,并不一定伴随重排。

重排:当DOM的变化影响了元素的几何属性(宽或高),浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为重排。重排一定伴随着重绘。

2. 触发重排的操作:

2.1 修改DOM元素几何属性:

修改元素大小,位置,内容(一般只有重绘,但是内容可能导致元素大小变化)

2.2 DOM树结构发生变化

当DOM树的结构变化时,例如节点的增减、移动等,也会触发重排。浏览器引擎布局的过程,类似于树的前序遍历,是一个从上到下从左到右的过程。 通常在这个过程中,当前元素不会再影响其前面已经遍历过的元素。所以,如果在body最前面插入一个元素,会导致整个文档的重新渲染,而在其后插入一个元 素,则不会影响到前面的元素。

2.4 改变浏览器大小

3.渲染树变化的排队和刷新

思考下面代码:

var ele = document.getElementById('myDiv');
ele.style.borderLeft = '1px';
ele.style.borderRight = '2px';
// var _top = ele.offsetTop; //刷新队列
ele.style.padding = '5px';

三行代码,三次修改元素的几何属性,浏览器应该发生三次重排重绘。

但是浏览器并不会这么笨,它也是有做优化的。它会把三次修改“保存”起来(大多数浏览器通过队列化修改并批量执行来优化重排过程,也有设置时间片段的),一次完成!

然而,如果你在三行代码中,以下获取DOM布局信息。(为了返回最新的布局信息,将立即执行渲染树变化队列的更新)

如上面被注释的第4行,如果取消注释会导致(2+3)、(5)两次重排;

获取关于DOM布局信息的属性:

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

 

4 应对方法:尽量减少重绘次数、减少重排次数、缩小重排的影响范围。

4.1 合并多次操作,如上面的操作

ele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

4.2 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。

4.3 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。但是这可能导致浏览器的闪烁。

4.4 在内存中多次操作节点,完成后再添加到文档中去(可使用fragment元素)。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html片段,再一次性添加到文档中去,而不是循环添加每一行。

var fragment = document.createDocumentFragment();  // 未使用的虚拟节点,appendChild(fragment) //append的是里面的子元素
 
var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);
 
var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);
 
document.getElementById('fruit').appendChild(fragment);

以上就是小编为大家带来的浅谈DOM的操作以及性能优化问题-重绘重排全部内容了,希望大家多多支持三水点靠木~

Javascript 相关文章推荐
select 控制网页内容隐藏于显示的实现代码
May 25 Javascript
原生js实现复制对象、扩展对象 类似jquery中的extend()方法
Aug 30 Javascript
javascript中setAttribute()函数使用方法及兼容性
Jul 19 Javascript
用JS写的一个Ajax库(实例代码)
Aug 06 Javascript
微信小程序 数据访问实例详解
Oct 08 Javascript
jQuery 开发之EasyUI 添加数据的实例
Sep 26 jQuery
基于axios封装fetch方法及调用实例
Feb 05 Javascript
使用vue2实现购物车和地址选配功能
Mar 29 Javascript
微信小程序开发之左右分栏效果的实例代码
May 20 Javascript
jQuery实现验证用户登录
Dec 10 jQuery
ES6使用 Array.includes 处理多重条件用法实例分析
Mar 02 Javascript
原生js实现表格翻页和跳转
Sep 29 Javascript
完美解决node.js中使用https请求报CERT_UNTRUSTED的问题
Jan 08 #Javascript
jQuery+ajax的资源回收处理机制分析
Jan 07 #Javascript
JS实现iframe自适应高度的方法示例
Jan 07 #Javascript
JS小数转换为整数的方法分析
Jan 07 #Javascript
jQuery日程管理插件fullcalendar使用详解
Jan 07 #Javascript
基于jquery二维码生成插件qrcode
Jan 07 #Javascript
jquery validation验证表单插件
Jan 07 #Javascript
You might like
php设计模式 Mediator (中介者模式)
2011/06/26 PHP
php实现json编码的方法
2015/07/30 PHP
php给图片加文字水印
2015/07/31 PHP
PHP编程之设置apache虚拟目录
2016/07/08 PHP
适合PHP初学者阅读的4本经典书籍
2016/09/23 PHP
php实现购物车产品删除功能(2)
2020/07/23 PHP
php框架CodeIgniter使用redis的方法分析
2018/04/13 PHP
php使用pthreads v3多线程实现抓取新浪新闻信息操作示例
2020/02/21 PHP
jQuery动画animate方法使用介绍
2013/05/06 Javascript
一览画面点击复选框后获取多个id值的方法
2016/05/30 Javascript
jQuery页面加载初始化的3种方法(推荐)
2016/06/02 Javascript
jQuery Validate验证表单时多个name相同的元素只验证第一个的解决方法
2016/12/24 Javascript
jQuery基于xml格式数据实现模糊查询及分页功能的方法
2016/12/25 Javascript
80%应聘者都不及格的JS面试题
2017/03/21 Javascript
详解react阻止无效重渲染的多种方式
2018/12/11 Javascript
40行代码把Vue3的响应式集成进React做状态管理
2020/05/20 Javascript
手把手带你搭建一个node cli的方法示例
2020/08/07 Javascript
[01:45]2014DOTA2 TI预选赛预选赛 战前探营!
2014/05/21 DOTA
Python基于Tkinter模块实现的弹球小游戏
2018/12/27 Python
六行python代码的爱心曲线详解
2019/05/17 Python
python pandas时序处理相关功能详解
2019/07/03 Python
Python之数据序列化(json、pickle、shelve)详解
2019/08/30 Python
复化梯形求积分实例——用Python进行数值计算
2019/11/20 Python
Python基础之列表常见操作经典实例详解
2020/02/26 Python
python定义具名元组实例操作
2021/02/28 Python
CSS3支持IE6, 7, and 8的边框border属性
2012/12/28 HTML / CSS
英国最大的经认证的有机超市:Planet Organic
2018/02/02 全球购物
英国品牌男装折扣网站:Brown Bag
2018/03/08 全球购物
学校七一活动方案
2014/01/19 职场文书
学校门卫管理制度
2014/01/30 职场文书
旅游个人求职信范文
2014/01/30 职场文书
民事起诉书范本
2015/05/19 职场文书
雨中的树观后感
2015/06/03 职场文书
黑暗中的舞者观后感
2015/06/18 职场文书
压缩Redis里的字符串大对象操作
2021/06/23 Redis
解决SpringBoot文件上传临时目录找不到的问题
2021/07/01 Java/Android