浅谈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 相关文章推荐
jQuery入门问答 整理的几个常见的初学者问题
Feb 22 Javascript
JS 实现导航栏悬停效果
Sep 23 Javascript
javascript教程之不完整的继承(js原型链)
Jan 13 Javascript
原生javascript实现拖动元素示例代码
Sep 01 Javascript
javascript排序函数实现数字排序
Jun 26 Javascript
js模仿php中strtotime()与date()函数实现方法
Aug 11 Javascript
微信小程序 循环及嵌套循环的使用总结
Sep 26 Javascript
利用jQuery+localStorage实现一个简易的计时器示例代码
Dec 25 jQuery
vue模块拖拽实现示例代码
Mar 09 Javascript
vue实现带复选框的树形菜单
May 27 Javascript
解决vue elementUI中table里数字、字母、中文混合排序问题
Jan 07 Javascript
Vue项目利用axios请求接口下载excel
Nov 17 Vue.js
完美解决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
模拟OICQ的实现思路和核心程序(二)
2006/10/09 PHP
PHP反射类ReflectionClass和ReflectionObject的使用方法
2013/11/13 PHP
yii2 上传图片的示例代码
2018/11/02 PHP
javascript firefox兼容ie的dom方法脚本
2008/05/18 Javascript
javascript之querySelector和querySelectorAll使用说明
2011/10/09 Javascript
JavaScript高级程序设计 阅读笔记(十三) js定义类或对象
2012/08/14 Javascript
JavaScript 模拟类机制及私有变量的方法及思路
2013/07/10 Javascript
js里取容器大小、定位、距离等属性搜集整理
2013/08/19 Javascript
jQuery的cookie插件实现保存用户登陆信息
2014/04/15 Javascript
浅析script标签中的defer与async属性
2016/11/30 Javascript
JavaScript中一些特殊的字符运算
2017/08/17 Javascript
微信小程序数据存储与取值详解
2018/01/30 Javascript
JavaScript中callee和caller的区别与用法实例分析
2019/06/28 Javascript
ES6如何用一句代码实现函数的柯里化
2020/01/18 Javascript
纯js+css实现在线时钟
2020/08/18 Javascript
如何手动实现一个 JavaScript 模块执行器
2020/10/16 Javascript
Python中使用装饰器时需要注意的一些问题
2015/05/11 Python
python中redis的安装和使用
2016/12/04 Python
Python调用C# Com dll组件实战教程
2017/10/12 Python
Python实用技巧之利用元组代替字典并为元组元素命名
2018/07/11 Python
python numpy数组的索引和切片的操作方法
2018/10/20 Python
py-charm延长试用期限实例
2019/12/22 Python
python itsdangerous模块的具体使用方法
2020/02/17 Python
Python迭代器Iterable判断方法解析
2020/03/16 Python
在python中利用pycharm自定义代码块教程(三步搞定)
2020/04/15 Python
python os.rename实例用法详解
2020/12/06 Python
企业厂长岗位职责
2013/12/17 职场文书
中级会计职业生涯规划书
2014/03/01 职场文书
团支部建设方案
2014/05/02 职场文书
励志演讲稿800字
2014/08/21 职场文书
乡党委干部党的群众路线教育实践活动个人对照检查材料思想汇报
2014/10/01 职场文书
习总书记三严三实学习心得体会
2014/10/13 职场文书
客户答谢会致辞
2015/01/20 职场文书
加薪通知
2015/04/25 职场文书
2015年保卫科工作总结
2015/05/14 职场文书
获奖感言范文
2015/07/31 职场文书