JS性能优化——DOM编程
浏览器中的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编程的更多相关文章
- JavaScript性能优化 DOM编程
最近在研读<高性能JavaScript>,在此做些简单记录.示例代码可在此处查看到. 一.DOM 1)DOM和JavaScript 文档对象模型(DOM)是一个独立于语言的,用于操作XML ...
- js 性能优化 篇一
JS性能优化 摘自:http://www.china125.com/design/js/3631.htm 首先,由于JS是一种解释型语言,执行速度要比编译型语言慢得多.(注:,Chrome是第一款内 ...
- js性能优化文章集锦
总结的js性能优化方面的小知识http://www.it165.net/pro/html/201503/35336.html 如何优化你的JS代码http://www.php100.com/html/ ...
- js性能优化-事件委托
js性能优化-事件委托 考虑一个列表,在li的数量非常少的时候,为每一个li添加事件侦听当然不会存在太多性能方面的问题,但是当列表非常的长,长到上百上千甚至上万的时候(当然只是一个解释,实际工作中很少 ...
- js 性能优化利器:prepack
1. js 性能优化 js 本身是没有像 python 一样的预编译功能,更没有像 java 一样的编译功能,所以,这里所说的 js 代码预编译 只是通过工具实现的类似功能而已. 这就要提到 prep ...
- Web篇之JS性能优化
首先,性能优化分好几个方面,本章我们从js方面来优化. 1:垃圾收集 日常中的某些情况下垃圾收集器无法回收无用变量,导致的一个结果就是——内存使用率不断增高,以下为对应的情况以及处理方法. ①对象相互 ...
- JS性能优化笔记搜索整理
通过网上查找资料了解关于性能优化方面的内容,现简单整理,仅供大家在优化的过程中参考使用,如有什么问题请及时提出,再做出相应的补充修改. 一. 让代码简洁:一些简略的表达方式也会产生很好的优化 eg:x ...
- js性能优化--学习笔记
<高性能网站建设进阶指南>: 1.使用局部变量,避免深入作用域查找,局部变量是读写速度最快的:把函数中使用次数超过一次的对象属性和数组存储为局部变量是一个好方法:比如for循环中的.len ...
- 你不知道的Node.js性能优化,读了之后水平直线上升
本文由云+社区发表 "当我第一次知道要这篇文章的时候,其实我是拒绝的,因为我觉得,你不能叫我写马上就写,我要有干货才行,写一些老生常谈的然后加上好多特技,那个 Node.js 性能啊好像 D ...
随机推荐
- Fresco,Facbook强大的图片加载框架
项目git地址:https://github.com/facebook/fresco Fresco是 facebook推出的一款强大的图片加载的框架:主要有Image Pipeline和Drawees ...
- tomcat使用安全及CVE-2017-12615
tomcat安全情报的收集 1.首先定期查看官网各个版本存在的安全漏洞公告: http://tomcat.apache.org/security.html 2.去各大漏洞网站查看漏洞披露信息 看几个漏 ...
- 2017.2.28 activiti实战--第六章--任务表单(一)动态表单
学习资料:<Activiti实战> 第六章 任务表单(一)动态表单 内容概览:本章要完成一个OA(协同办公系统)的请假流程的设计,从实用的角度,讲解如何将activiti与业务紧密相连. ...
- 百科知识 国内的创业项目如何众筹,能登录Kickstarter吗
一个国内的团队登陆Kickstarter到底有多难? 300万用户,4.8亿美元筹款,Kickstarter在2013年交出了一份惊艳的答卷.对于美英澳加新荷六国的创业团队来说,Kickstarter ...
- blind xxe攻击
最近做啊里的题的时候遇到了 http://hivesec.net/web-security/%E5%85%B3%E4%BA%8Eblind-xxe.html
- mysql中UNIX_TIMESTAMP()函数和php中time()函数的区别
http://tech.ddvip.com/2009-01/1231392775105351.html mysql 中:UNIX_TIMESTAMP(), UNIX_TIMESTAMP(date) 若 ...
- Java使用笔记之对象比较
1.关于java对象的比较,经常会遇见比较某个两个对象的多个属性是否相等,可以通过重写对象equals方法来实现. 比如有两个User,如果姓名和年龄相等的话,我们就可以认为他们重复的数据.那么我们就 ...
- 如何用openssl命令行生成证书
老大要我在web server里面加上https功能,可是但就使用openssl生成证书就耗费了我两天时间,网上有很多相关的资料,但是都写的很复杂, 照着他们的来,还是走了很多弯路. 我总结一下,步骤 ...
- Hibernate demo之使用注解
1.新建maven项目 testHibernate,pom.xml <?xml version="1.0" encoding="UTF-8"?> & ...
- Zabbix-20160817-高危SQL注入漏洞
漏洞概述: zabbix是一个开源的企业级性能监控解决方案.近日,zabbix的jsrpc的profileIdx2参数存在insert方式的SQL注入漏洞,攻击者无需授权登陆即可登陆zabbix管理系 ...