浅谈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 相关文章推荐
(JS实现)MapBar中坐标的加密和解密的脚本
May 16 Javascript
jquery cookie的用法总结
Nov 18 Javascript
jquery合并表格中相同文本的相邻单元格
Jul 17 Javascript
js实现select选择框效果及美化
Aug 19 Javascript
js中小数向上取整数,向下取整数,四舍五入取整数的实现(必看篇)
Feb 13 Javascript
MUI实现上拉加载和下拉刷新效果
Jun 30 Javascript
详解angularjs实现echart图表效果最简洁教程
Nov 29 Javascript
详解如何在nuxt中添加proxyTable代理
Aug 10 Javascript
解决Vue.js由于延时显示了{{message}}引用界面的问题
Aug 25 Javascript
Node.js + express基本用法教程
Mar 14 Javascript
jquery+css3实现的经典弹出层效果示例
May 16 jQuery
vue+openlayers绘制省市边界线
Dec 24 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
SONY SRF-40W电路分析
2021/03/02 无线电
回答PHPCHINA上的几个问题:URL映射
2007/02/14 PHP
php缓存技术详细总结
2013/08/07 PHP
php获取目标函数执行时间示例
2014/03/04 PHP
UI Events 用户界面事件
2012/06/27 Javascript
基于MVC3方式实现下拉列表联动(JQuery)
2013/09/02 Javascript
JavaScript框架(iframe)操作总结
2014/04/16 Javascript
JS网页在线获取鼠标坐标值的方法
2015/02/28 Javascript
jquery实现瀑布流效果 jquery下拉加载新数据
2016/12/12 Javascript
jquery replace方法去空格
2017/05/08 jQuery
详解vue 单页应用(spa)前端路由实现原理
2018/04/04 Javascript
vue自定义指令实现方法详解
2019/02/11 Javascript
Layui弹出层 加载 做编辑页面的方法
2019/09/16 Javascript
React学习之受控组件与数据共享实例分析
2020/01/06 Javascript
JS实现电商商品展示放大镜特效
2020/01/07 Javascript
vue中el-input绑定键盘按键(按键修饰符)
2020/07/22 Javascript
[01:01:31]2018DOTA2亚洲邀请赛3月29日小组赛B组 Mineski VS paiN
2018/03/30 DOTA
python实现简单的计时器功能函数
2015/03/14 Python
Python编程实现二叉树及七种遍历方法详解
2017/06/02 Python
PyQt5创建一个新窗口的实例
2019/06/20 Python
通过celery异步处理一个查询任务的完整代码
2019/11/19 Python
Python Pillow(PIL)库的用法详解
2020/09/19 Python
Django多数据库联用实现方法解析
2020/11/12 Python
python 爬虫如何实现百度翻译
2020/11/16 Python
基于CSS3实现立方体自转效果
2016/03/01 HTML / CSS
家乐福巴西网上超市:Carrefour巴西
2016/10/31 全球购物
.net软件工程师应聘上机试题
2015/03/10 面试题
行政助理求职自荐信
2013/10/26 职场文书
体育教育专业自荐信范文
2013/12/20 职场文书
农村门前三包责任书
2014/07/25 职场文书
英语导游欢迎词
2015/09/30 职场文书
2016道德模范先进事迹材料
2016/02/26 职场文书
会议主持词通用版
2019/04/02 职场文书
浅谈:电影《孔子》观后感(范文)
2019/10/14 职场文书
解决vue $http的get和post请求跨域问题
2021/06/07 Vue.js
Redis+Lua脚本实现计数器接口防刷功能(升级版)
2022/02/12 Redis