浅谈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 相关文章推荐
2010年最佳jQuery插件整理
Dec 06 Javascript
jQuery中extend函数详解
Feb 13 Javascript
JS实现屏蔽shift,Ctrl,alt等功能键的方法
Jun 01 Javascript
jQuery实现的倒计时效果实例小结
Apr 16 Javascript
使用jQuery.form.js/springmvc框架实现文件上传功能
May 12 Javascript
jQuery操作cookie
Aug 08 Javascript
jQuery实现的checkbox级联选择下拉菜单效果示例
Dec 26 Javascript
JavaScript用JSONP跨域请求数据实例详解
Jan 06 Javascript
简单谈谈关于Angular Cli打包的事
Sep 05 Javascript
jQuery进阶实践之利用最优雅的方式如何写ajax请求
Dec 20 jQuery
基于Vuejs的搜索匹配功能实现方法
Mar 03 Javascript
Elasticsearch实现复合查询高亮结果功能
Sep 10 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+AJAX传送中文会导致乱码的问题的解决方法
2008/09/08 PHP
php判断str字符串是否是xml格式数据的方法示例
2017/07/26 PHP
php解压缩zip和rar压缩包文件的方法
2019/07/10 PHP
asp 取文本框名称代码
2008/12/02 Javascript
JavaScript 保存数组到Cookie的代码
2010/04/14 Javascript
jquery实现鼠标滑过小图查看大图的方法
2015/07/20 Javascript
AngularJS中的$watch(),$digest()和$apply()区分
2016/04/04 Javascript
js模拟百度模糊搜索的实例
2017/08/04 Javascript
Javascript(es2016) import和require用法和区别详解
2017/08/11 Javascript
nodejs结合Socket.IO实现的即时通讯功能详解
2018/01/12 NodeJs
jQuery 点击获取验证码按钮及倒计时功能
2018/09/20 jQuery
js canvas实现橡皮擦效果
2018/12/20 Javascript
如何使用less实现随机下雪动画详解
2019/01/02 Javascript
浅谈小程序 setData学问多
2019/02/20 Javascript
js实现鼠标拖拽div左右滑动
2020/01/15 Javascript
[01:06]DOTA2亚洲邀请赛专属珍藏-荧煌之礼
2017/03/24 DOTA
Python使用Flask框架同时上传多个文件的方法
2015/03/21 Python
在Python中使用__slots__方法的详细教程
2015/04/28 Python
浅谈用Python实现一个大数据搜索引擎
2017/11/28 Python
微信跳一跳python自动代码解读1.0
2018/01/12 Python
Python实现在某个数组中查找一个值的算法示例
2018/06/27 Python
django-初始配置(纯手写)详解
2019/07/30 Python
Django之模板层的实现代码
2019/09/09 Python
python3代码输出嵌套式对象实例详解
2020/12/03 Python
python Protobuf定义消息类型知识点讲解
2021/03/02 Python
CSS实现聊天气泡效果
2020/04/26 HTML / CSS
印尼综合在线预订网站:Tiket.com(机票、酒店、火车、租车和娱乐)
2018/10/11 全球购物
文秘班元旦晚会活动策划方案
2014/08/28 职场文书
2015毕业寄语大全
2015/02/26 职场文书
研究生简历自我评
2015/03/11 职场文书
家庭聚会祝酒词
2015/08/11 职场文书
小学班级标语口号大全
2015/12/26 职场文书
导游词之鲁迅祖居
2019/10/17 职场文书
Matlab如何实现矩阵复制扩充
2021/06/02 Python
浅谈PostgreSQL表分区的三种方式
2021/06/29 PostgreSQL
Python内置数据类型中的集合详解
2022/03/18 Python