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

写在前面:

大家都知道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);
////////////////////////

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的操作以及性能优化问题-重绘重排全部内容了,希望大家多多支持我们~

(0)

相关推荐

  • 高性能JavaScript 重排与重绘(2)

    先回顾下前一篇文章高性能JavaScript DOM编程,主要提了两点优化,一是尽量减少DOM的访问,而把运算放在ECMAScript这一端,二是尽量缓存局部变量,比如length等等,最后介绍了两个新的API querySelector()以及querySelectorAll(),在做组合选择的时候可以大胆使用.而本文主要讲的是DOM编程可能最耗时的地方,重排和重绘. 1.什么是重排和重绘 浏览器下载完页面中的所有组件--HTML标记.JavaScript.CSS.图片之后会解析生成两个内部数

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

    写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScript)各自想象为一个岛屿,它们之间用收费桥梁连接,ECMAScript每次访问DOM,都要途径这座桥,并交纳"过桥费",访问DOM的次数越多,费用也就越高.因此,推荐的做法是尽量减少过桥的次数,努力待在ECMAScript岛上.我们不可能不用DOM的接口,那么,怎样才能提高程序的效率? 既然

  • 浅谈.NET反射机制的性能优化 附实例下载

    可能大家谈到反射面部肌肉都开始抽搐了吧!因为在托管语言里面,最臭名昭著的就是反射!它的性能实在是太低了,甚至在很多时候让我们无法忍受.不过不用那么纠结了,老陈今天就来分享一下如何来优化反射! 概述 本文涉及到的反射优化的途径有如下两种: 通过Delegate.CreateDelegate()创建委托进行优化 通过.NET4的动态运行时进行优化 如果您还知道其他更加有效的优化途径,请不吝赐教! 准备工作 今天我们总计要对比五种不同的调用对象成员的方式,也算是一种性能测评. 在开始之前,我们首先定义

  • 浅谈Spring Boot Web 应用性能优化

    默认情况下,Spring Boot Web 应用会装配一些功能组件 Bean. 在大多数 Web 应用场景下,可以选择性地关闭一下自动装配的Spring 组件 Bean,以达到提升性能的目的. 配置项优化 Spring Boot Web 应用加速 完整配置项 management.add-application-context-header = false spring.mvc.formcontent.putfilter.enabled = false spring.autoconfigure.

  • 浅谈vue首屏加载优化

    本文介绍了浅谈vue首屏加载优化,分享给大家,具体如下: 库使用情况 vue vue-router axios muse-ui material-icons vue-baidu-map 未优化前 首先我们在正常情况下build 优化 1. 按需加载 当前流行的UI框架如iview,muse-ui,Element UI都支持按需加载,只需稍微改动一下代码. 修改前: import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' imp

  • 再谈Angular4 脏值检测(性能优化)

    Summary Angular 4的脏值检测是个老话题了,而理解这个模型是做Angular性能优化的基础.因此,今天我们再来聊聊Angular 4脏值检测的原理,并看看性能优化的小提示. 进入点 - Zone.js Angular 4是一个MVVM框架.数据模型(Model)转换成视图模型(ViewModel)后,绑定到视图(View)上渲染成肉眼可见的页面.因此,发现数据模型变化的时间点是更新页面的关键,也是调用脏值检测的关键. 经过分析,工程师们发现,数据的变化往往由macrotask和mi

  • 浅谈C# StringBuilder内存碎片对性能的影响

    StringBuilder内部是由多段char[]组成的半自动链表,因此频繁从中间修改StringBuilder,会将原本连续的内存分隔为多段,从而影响读取/遍历性能. 连续内存与不连续内存的性能差,可能高达1600倍. 背景 用StringBuilder的用户可能大都想用StringBuilder拼接html/json模板.组装动态SQL等正常操作.但在一些特殊场景中--如为某种编程语言写语言服务,或者写一个富文本编辑器时,StringBuilder依然也有用武之地,通过里面的Insert/R

  • 浅谈Java编程之if-else的优化技巧总结

    一.使用策略枚举来优化if-else 看到网上蛮多人推荐使用策略模式来优化if-else,但我总觉得,搞一堆策略类来优化大批量if-else,虽然想法很好,但无意之中很可能又会创造出很多类对象,就显得过于繁重了.若想使用策略模式来优化大批量if-else,其实有一种更好的方式,这是策略模式+枚举方式的改良 二.使用三目运算符来优化if-else 1.根据if-else条件来判断赋值的,如: String id=""; if(flag){ id="a"; }else{

  • 浅谈MySQL分页Limit的性能问题

    MySQL的分页查询通常通过limit来实现.limit接收1或2个整数型参数,如果是2个参数,第一个是指定第一个返回记录行的偏移量,第二个是返回记录行的最大数目.初始记录行的偏移量是0.为了与PostgreSQL兼容,limit也支持limit # offset #. 问题: 对于小的偏移量,直接使用limit来查询没有什么问题,但随着数据量的增大,越往后分页,limit语句的偏移量就会越大,速度也会明显变慢. 优化思想:避免数据量大时扫描过多的记录 解决:子查询的分页方式或者JOIN分页方式

  • ASP.NET技巧:同时对多个文件进行大量写操作对性能优化

    我自己的一个项目,需要同时对65536个文件进行多次写操作. 如果先全部打开所有的文件,然后重复写,最后关闭所有的文件.那么第一次写操作全部完成需要16分钟左右,而第二次就需要40分钟了.没有继续测试了. for (int i = 0; i < 65536; i++)            {                fileStream[i] = new FileStream(buffDir+"\\"+ i.ToString() + ".dat", F

  • 浅谈页面装载js及性能分析方法

    一.装载 先装载静态页面的引用js文件,然后查找引用文件中是否包含onload函数,比如main.js中包含onload函数,在main.js中查找是否有对其他js文件的引用,优先装载引用js文件,被引用中文件的装载顺序和main.js的顺序一致. 装载完毕后,开始执行onload函数.由于js执行顺序是顺序执行的,为提高页面相应速度,一般做法是在onload中只画页面,一些事件的绑定函数,ajax方法等可延后书写. 二.响应速度分析 1.借助工具进行分析 各大浏览器的开发者工具(最喜欢使用火狐

随机推荐