浏览器中的DOM  天生就慢

DOM是个与语言无关的API,它在浏览器中的接口却是用JavaScript实现的。客户端脚本编程大多数时候是在个底层文档打交道,DOM就成为现在JavaScript编码中的重要部分。

DOM访问和修改

ECMAScript 每次访问DOM 都会产生性能损耗。

修改元素则更为昂贵,因为它会导致浏览器重新计算页面的几何变换。

最坏的情况是在循环中访问或修改元素,尤其是对HTML元素集合循环操作。

function innerHtmlLoop(){
for(var count = 0; count <15000; count++){
document.getElementById('here').innerHTML +="a";
}
}

这个函数循环修改页面元素的内容,每次循环迭代,该元素都被访问两次:一次读取innerHtml的属性值,另一次重写它。

换一种效率更高的方法,用局部变量存储修改中的内容,再循环结束后一次性写入:

function innerHtmlLoop(){
var content = '';
for(var count = 0; count <15000; count++){
count += 'a';
}
document.getElementById('here').innerHTML += content;
}

这种方式比上边的快了155倍。

访问DOM的次数越多,代码的运行速度越慢。因此,通用的经验法则是:减少访问DOM的次数,把运算尽量留在ECMAScript这一端处理。

innerHTML对比DOM方法:推荐使用innerHTML 而不是原生DOM方法生成HTML,绝大部分浏览器中都是innerHTML运行的更快。但是对于大多数日常的操作而言,并没有太大的区别,所以根据可读性、稳定性、团队习惯、代码风格来综合决定使用哪种方式。

节点克隆:element.cloneNode()(element表示已有节点)替代document.createElement()。   在大多数浏览器中节点克隆更有效率,但是也不是特别明显。

HTML集合:是包含了DOM节点引用的类数组对象,eg:document.getElementsByName();...或者:document.images页面中所有的img元素document.links所有a元素...

      返回值为HTML集合对象,是个类数组的列表。但是并不是真正的数组(因为没有slice和push之类的方法),但是提供了一个类似数组中的length的属性,并且还能以数字索引的方式访问列表中的元素。

      遍历这种类数组的集合,读取元素集合的length属性会引发集合进行更新,这在所有的浏览器中都有明显的性能问题,优化方法:将集合的长度缓存到循环外的局部变量中,然后在循环的条件退出语句中使用该变量:

function loopCacheLengthCollection(){
var coll = document.getElementsByTagName('div');  //这里的coll是集合 类数组
len = coll.length;     //将集合的长度缓存到局部变量len中
for(var count = 0; count < len; count++){ //不要在这里写 count < coll.length,会明显影响性能 ,
                              //如果coll是数组,那么 count < coll.length 对性能影响并不大
/* 代码处理 */
}
}

遍历DOM:可以使用document.querySelector('.myclass')的方法来查询整个文档,活通过elref.querySelector('.myclass')在子树中进行查询,这里的elref是一个DOM元素的引用。

重绘与重排

浏览器在下载完页面中的所有组件---HTML标记、JavaScript、css、图片,之后会解析并生成两个内部数据结构:

DOM树:表示页面结构

渲染树:表示DOM节点如何显示

重排何时发生

    添加或删除可见的DOM元素;

    元素位置改变;

    元素尺寸改变(内外边距,边框厚度,宽高等);

    内容改变;文本改变或者图片被另一个不同尺寸的图片替代

    页面渲染器初始化;

    浏览器窗口尺寸改变

渲染树变化的排队与刷新

    offsetTop, offsetLeft, offsetWidth, offsetHeight

    scrollTop, scrollLeft, scrollWidth, scrollHeight

    clientTop, clientLeft, clientWidth, clientHeight

    getComputedStyle()(currentStyle in IE)

  这些方法需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的“待处理变化”并处罚重排以返回正确的值,在修改样式的过程中,最好避免使用上边列出的属性

var computed,
tmp = '',
bodystyle = document.body.style;
if(document.body.currentStyle){
computed = document.body.currentStyle;
} else{
computed = document.defaultView.getComputedStyle(document.body,'');
} //修改同一属性低效的方式
//然后获取样式信息
bodystyle.color = 'red';
tmp = computed.backgroundColor;
bodystyle.color = 'white';
tmp = computed.backgroundImage;
bodystyle.color = 'green';
tmp = computed.backgroundAttachment; //每次修改够都读取一个computed样式属性。读取的属性backgroundColor、backgroundImage、backgroundAttachment都与改变的颜色无关。然而浏览器却需要刷新渲染队列并重排,因为compited的样式属性被请求了。
//更有效的方法,性能更快。如下:
bodystyle.color = 'red';
bodystyle.color = 'white';
bodystyle.color = 'green';
tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;

最小化重绘和重排:重绘和重排可能代价非常昂贵,因此减少此类操作的发生。可以合并多次对DOM和样式的修改,然后依次处理掉。

var el = document.getElementById('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
//最糟糕情况下回导致浏览器触发三次重排。大部分浏览器为此做了优化,只会触发一次重排,但是如果在上边代码执行时,有其他代码请求布局信息,那么就会导致三次重排
//而且这段代码四次请求DOM,可以被优化: var el = document.getElementById('mydiv');
el.style.cssText = 'boeder-left: 1px; border-right: 2px; padding: 5px;';//如果不想覆盖原有的样式 可以写 el.style.cssText += ';boeder-left: 1px;';
//修改css的class名称,更易于维护,可能会带来轻微的性能问题,因为改变类时需要检查级联样式。
var el = document.getElementById('mydiv');
el.className = 'active';

批量修改DOM:当你对DOM元素进行一系列操作时,可以通过下边的步骤来减少重绘和重排:

      1、使元素脱离文档流

      2、对其应用多重改变

      3、把元素带回文档中。

      该过程会触发两次重排(①和③)。但是如果你忽略这两个步骤,那么在第二步所产生的任何修改都会触发一次重排

        使DOM脱离文档的三种基本方法:

          ①隐藏元素,应用修改,重新显示

//为了演示脱离文档的操作,考虑下边的链接列表,它必须更新更多的信息
<ul id = "mylist">
<li><a href = "http://phpied.com">Stoyan</a></li>
<li><a href = "http://julienlecomte.com">Stoyan</a></li>
</ul>
//假设附加数据已经存储在一个对象中,并要插入列表。这些数据定义如下: var data = [
{
"name": "Nicholas",
"url": "http://nczonline.net"
},
{
"name": "Ross",
"url": "http://techfoolery.com"
}
];
//下面是一个用来更新指定节点数据的通用函数:
function appendDataToElement(appendToElement, data){
var a, li;
for(var i = 0; max = data.length; i++){
a = document.createElement('a');
a.href = data[i].url;
a.appendChild(document.createTextNode(data[i].name));
li = document.createElement('li');
li.appendChild(a);
appendToElement.appendChild(li);
}
}
//最明显的方法:
var ul = document.gerElementById('mylist');
appendDataToElement(ul, data); //但是这种方法,data的每一个新条目被附加到当前DOM树时都会导致重排。
//第一种方法,改变display属性,临时从文档中移除<ul>元素,然后再回复它:
var ul = document.getElementById('mylist');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

          ②使用文档片段在当前DOM之外构建一个子树,再把它拷贝回文档(推荐使用)

var fragment = document.createDocumentFragment();
appendDataToElement(fragment,data);
docuemnt.getElementById(mylist').appendChild(fragment);

          ③将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素

var old = document.getElementById('mylist');
var clone = old.cloneNode(true);
appendDataToElement(old,data);
old.parentNode.replaceChild(clone,old);

缓存布局信息

    myElement.style.left = 1 + myElement.offsetLeft + 'px';
  --->  current++;  myElement.style.left = 1 + current + 'px';

让元素脱离动画流

  一般情况,重排只影响渲染树中的一小部分,但也可能影响很大的部分,甚至整个渲染树。浏览器所需要重排的次数越少,应用程序的响应速度就越快。

  因此当页面的一个动画推移页面整个余下的部分时,会导致一次代价昂贵的大规模重排,用户会感到页面一顿一顿的。渲染树中需要重新计算的节点越多,情况就会越糟。

  拒绝重排:1、使用绝对位置定位页面上的动画元素,将其脱离文档流。

       2、让元素动起来。当它扩大时,会临时覆盖部分页面,但这只是页面一个小区域的重绘过程,不会产生重排并重绘页面的大部分内容。

       3、当动画结束时恢复定位,从而只会下移一次文档的其他元素。

IE和:hover

  从IE7开始,IE允许在任何元素(严格模式)上使用 :hover 这个css伪选择器。但是如果大量使用 :hover,那么会降低响应速度。这个问题在IE8中更为明显。

  很大的表格或很长的列表,应避免使用这种效果。

事件委托

当页面中存在大量元素,而且每一个都要一次或者多次绑定事件处理器时,这种情况可能会影响性能。每绑定一个事件处理器都是有代价的 。需要访问和修改的DOM元素越多,应用程序也就越慢,特别是时间绑定通常发生在onload(或DOMContentReady)时,此时对每一个富交互应用的网页来说都是一个拥堵的时刻。事件绑定占用了处理的时间,而且,浏览器需要跟踪每个事件处理器,这也会占用更多的内存。当这些工作结束时,这些事件处理器中的绝大部分都不再需要(因为并不是100%的按钮或链接会被用户点击),因此有很多工作是没有必要的。

事件委托可以很好的处理这类问题。原理:事件逐层冒泡并能被父级元素捕获。使用事件代理,只需要给外层元素绑定一个处理器,就可以处理在其子元素上触发的所有事件。

  1、访问事件对象,并判断事件源

  2、取消文档树中的冒泡(可选)

  3、阻止默认动作(可选)

小结:

最小化DOM访问次数,尽可能在JavaScript端处理。

如果需要多次访问某个DOM节点,请使用局部变量存储它的引用

小心处理HTML集合,因为它实时连系着底层文档,把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。

如果可能的话,使用速度更快的API,比如querySelectorAll()和firstElementChild。

要留意重绘和重排;批量修改样式时,“离线”操作DOM树,使用缓存,并减少访问布局信息的次数

动画中使用绝对定位,使用拖放代理

使用事件委托来减少事件处理器的数量

JS性能优化——DOM编程的更多相关文章

  1. JavaScript性能优化 DOM编程

    最近在研读<高性能JavaScript>,在此做些简单记录.示例代码可在此处查看到. 一.DOM 1)DOM和JavaScript 文档对象模型(DOM)是一个独立于语言的,用于操作XML ...

  2. js 性能优化 篇一

    JS性能优化 摘自:http://www.china125.com/design/js/3631.htm  首先,由于JS是一种解释型语言,执行速度要比编译型语言慢得多.(注:,Chrome是第一款内 ...

  3. js性能优化文章集锦

    总结的js性能优化方面的小知识http://www.it165.net/pro/html/201503/35336.html 如何优化你的JS代码http://www.php100.com/html/ ...

  4. js性能优化-事件委托

    js性能优化-事件委托 考虑一个列表,在li的数量非常少的时候,为每一个li添加事件侦听当然不会存在太多性能方面的问题,但是当列表非常的长,长到上百上千甚至上万的时候(当然只是一个解释,实际工作中很少 ...

  5. js 性能优化利器:prepack

    1. js 性能优化 js 本身是没有像 python 一样的预编译功能,更没有像 java 一样的编译功能,所以,这里所说的 js 代码预编译 只是通过工具实现的类似功能而已. 这就要提到 prep ...

  6. Web篇之JS性能优化

    首先,性能优化分好几个方面,本章我们从js方面来优化. 1:垃圾收集 日常中的某些情况下垃圾收集器无法回收无用变量,导致的一个结果就是——内存使用率不断增高,以下为对应的情况以及处理方法. ①对象相互 ...

  7. JS性能优化笔记搜索整理

    通过网上查找资料了解关于性能优化方面的内容,现简单整理,仅供大家在优化的过程中参考使用,如有什么问题请及时提出,再做出相应的补充修改. 一. 让代码简洁:一些简略的表达方式也会产生很好的优化 eg:x ...

  8. js性能优化--学习笔记

    <高性能网站建设进阶指南>: 1.使用局部变量,避免深入作用域查找,局部变量是读写速度最快的:把函数中使用次数超过一次的对象属性和数组存储为局部变量是一个好方法:比如for循环中的.len ...

  9. 你不知道的Node.js性能优化,读了之后水平直线上升

    本文由云+社区发表 "当我第一次知道要这篇文章的时候,其实我是拒绝的,因为我觉得,你不能叫我写马上就写,我要有干货才行,写一些老生常谈的然后加上好多特技,那个 Node.js 性能啊好像 D ...

随机推荐

  1. Linux基础学习5

    程序管理与SELinux初探 process&program  程序 (program):通常为 binary program ,放置在储存媒体中 (如硬盘.光盘.软盘.磁带等), 为实体档案 ...

  2. DedeCMS使用channelartlist循环,不能获取外部链接的解决办法

    Dede在制作下拉菜单时,可以使用channelartlist循环调用顶级栏目和子栏目,但该标签不能获取外部链接, 下拉菜单时一段代码搞定多个顶级栏目和子级栏目的同时输出. <div>   ...

  3. sencha toucha获取 constructor中的数据

    config:{ tmp:null }, constructor : function(conf) { this.config.tmp=conf; } 添加配置属性,然后直接用 this.config ...

  4. Linux 设备驱动模型

    Linux系统将设备和驱动归一到设备驱动模型中了来管理 设备驱动程序功能: 1,对硬件设备初始化和释放 2,对设备进行管理,包括实参设置,以及提供对设备的统一操作接口 3,读取应用程序传递给设备文件的 ...

  5. VS2010 C#调用C++ DLL文件 【转】

    http://www.soaspx.com/dotnet/csharp/csharp_20110406_7469.html 背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第 ...

  6. 关于异步请求AJAX的具体解释

    1,异步请求的方法步骤: 1,推断当前用户支持的浏览器类型 XMLHttpRequest:推断是否支持非IE浏览器,相应的创建方法:xmlhttp = new XMLHttpRequest(); wi ...

  7. HTML5 Canvas 绘制加拿大枫叶旗

    这段代码比较简单,中间的枫叶使用了图片,因为没找到画法: <!DOCTYPE html> <html lang="utf-8"> <meta http ...

  8. 雪习新知识:Java 内部类

    本文出自 http://blog.csdn.net/zhaizu/article/details/49176543,转载请注明出处. 嵌套类,内部类,静态内部类,静态嵌套类.匿名类,成员类,局部类,傻 ...

  9. HDU 5301(Buildings-贪心构造)

    Buildings Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Tota ...

  10. TextView上的文字逐渐变淡直到消失

    给TextView加个动画效果,完了在个动画加个监听,里面有个动画执行完调用的方法在方法里面把TextView设置为gone,我觉得你直接加这个动画效果之后他就会不显示了,其实他还在那占有位置呢.想不 ...