上篇我介绍了Javascript标识符查找方面的优化,能够看出在这方面的优化给性能带来的提高并不明显,甚至能够说基本没有影响。可是,我今天要分享的是前端Javascript优化的一个大头。众所周知,在浏览器端Javascript中DOM操做相比普通Javascript代码来讲是比较耗时的,因此在DOM优化上下功夫能够收到至关可观的性能优化。下面我将分享几个DOM方面的性能优化策略。javascript
浏览器中的Javascript能够分为两个部分:ECMAScript和DOM API。而相比原生的ECMAScript来讲,DOM API会耗时不少。咱们能够把这两部分想象成两个经过桥梁链接的小岛,在ECMAScript小岛上进行的操做运行速度比在DOM小岛上面的操做要快不少,每次在进行DOM操做的时候你都须要从ECMAScript这个小岛经过这个桥梁到达DOM小岛上而后在上面进行耗时的操做。因此大量的DOM操做就会下降性能。
你们先看看下面这个例子:css
//优化前 var start = new Date().getTime() ; for(var i = 0 ; i < length ; i ++){ document.getElementById("test").innerHTML += "a" ; } console.log("Before:" + (new Date().getTime() - start)) ; //优化后 start = new Date().getTime() ; var content = "" ; for(var i = 0 ; i < length ; i ++){ content += "a" ; } document.getElementById("test").innerHTML += content ; console.log("After:" + (new Date().getTime() - start)) ;
从运行结果来看,能够说差距那是至关明显啊:前端
优化前的代码每一次循环都进行了DOM操做,而优化以后,只在最后一步进行了DOM操做,这就是DOM优化的力量啊。因此,咱们应该在操做的时候尽可能避免对DOM的操做,能少操做DOM就少操做。按照上面的比喻就比如是,咱们经过桥梁从ECMAScript小岛到达DOM小岛,而后找出须要进行操做的元素,把它再带回到ECMAScript小岛进行操做,经过这个方式,能够加快操做的速度,咱们应该尽量多的把元素带回到ECMAScript小岛进行操做。java
在页面上动态添加结点通常有两个方法:innerHTML和createElement方法。这两个方法在性能上也有一点差异,具体差异在哪儿呢?上代码:git
var start = new Date().getTime() ; var content = "<div>" ; for(var i = 0 ; i < 1000 ; i ++){ content += "<div></div>" ; } content += "</div>" ; document.getElementById("test").innerHTML += content ; console.log("innerHTML:" + (new Date().getTime() - start)) ; document.getElementById("test").innerHTML = "" ; start = new Date().getTime() ; //为了不直接往test节点上面添加节点引发的页面重画,因此使用一个div节点来存储添加的节点,最后把div添加到页面中 var div = document.createElement("div") ; for(var i = 0 ; i < 1000 ; i ++){ div.appendChild(document.createElement("div")) ; } document.getElementById("test").appendChild(div) ; console.log("createElement:" + (new Date().getTime() - start)) ;
这段代码在不一样浏览器上的运行结果是不同的:github
在Chrome上createElement比innerHTML快,而在Firefoxhe和IE上结果则相反,从结果上看彷佛是innerHTML以2:1赢了,但是我仍是建议你们使用createElement,我把上面的代码改为下面这样:segmentfault
var start = new Date().getTime() ; var test = document.getElementById("test") ; for(var i = 0 ; i < 1000 ; i ++){ test.innerHTML += "<div></div>" ; } console.log("innerHTML:" + (new Date().getTime() - start)) ; document.getElementById("test").innerHTML = "" ; start = new Date().getTime() ; for(var i = 0 ; i < 1000 ; i ++){ test.appendChild(document.createElement("div")) ; } console.log("createElement:" + (new Date().getTime() - start)) ;
上面这段代码的运行结果数组
能够看出来innerHTML和createElement差不少。为了测试我用了比较大的数据1000,在实际开发中通常不会出现这种状况,因此性能上的差别也就不会那么明显,可是除了考虑性能问题之外,咱们还应该考虑代码的可读性以及可维护下方面的问题,而考虑到这些的话,我我的仍是比较推荐使用createElement,若是你们有什么别的见解,欢迎一块儿讨论。浏览器
HTMLCollection是若干个DOM节点的集合,它具备数组的一些特性,好比length属性、经过下标访问,可是它并非数组,它没有push和slice方法。在DOM操做中咱们常常会用到HTMLCollection,下面的方法都会返回HTMLCollection:缓存
getElementsByName
getElementsByTagName
getElementsByClassName
document.forms
document.images
document.links
还有一些别的方法和属性会返回HTMLCollection,在这里就不一一列举了。如何处理它们也是影响性能的一个方面。优化策略跟上面的大同小异,就是用局部变量缓存集合以及集合的长度,我就不进行实际测试了。HTMLCollection还有一个很重要的特性就是它是根据页面的状况动态更新的,若是你更新的页面那么它的内容也会发生变化。好比下面这段代码:
var divs = document.getElementsByTagName("div") ; for(var i = 0 ; i < divs.length ; i ++){ document.body.appendChild(document.createElement("div")) ; }
这段代码的原意是向body中添加多一倍的div节点,可是真正的运行会致使死循环,这就是由于divs是动态更新的,每次向body中添加div节点都会使length属性发生变化也就是加1,因此这个循环会一直执行下去,在开发的时候应该注意这个问题。一个理想的办法就是缓存divs的长度,这样就不会引发死循环了。
若是须要获得某个节点的因此孩子节点,咱们可能会用到childNodes属性;获得第一个孩子,咱们可能会用到firstChild;获得下一个兄弟节点,咱们可能会用到nextSibling。可是这些属性都存在一些问题就是它们会把一些空格和空行也看成孩子节点返回给咱们,而这些常常不是咱们所想要的,若是使用这些属性那么咱们就须要对它们进行筛选,这样势必会影响效率。因此咱们应该用别的属性来替代这些,看下表:
表格左边的是推荐的属性,它们只会返回Element节点。不过并非全部浏览器都支持,因此在使用以前咱们须要先判断一下。
现代浏览器给咱们提供了另一种方法在获取咱们须要的节点,这个方法是querySelectorAll和querySelector。它们经过CSS选择器做为参数,返回知足条件的节点。querySelectorAll方法返回知足条件的全部节点而querySelector返回知足条件的第一个节点。使用这两个方法来替代咱们之前常常用的getElementById,getElementsByTagName等方法也是提升性能的一个途径。不过仍是老问题,并非全部浏览器都支持这两个方法,全部仍是先作个判断吧。
首先,Repaint是指页面上的元素的外观发生了改变可是不影响布局的状况下引发的浏览器从新绘画元素外观的行为,好比修改color,background-color等属性。Reflow是指页面上的元素的大小布局发生的变化从而引发浏览器对页面其余元素位置大小进行从新计算而且布局的行为。Reflow所致使的性能消耗远比Repaint大,因此咱们下面重点讨论Reflow状况下的优化策略。
在讨论Reflow以前先简单的看一下浏览器加载页面的过程。以下图:
浏览器在收到HTML文档以后对其进行解析,解析过程分为两个部分DOM文档的解析和CSS样式的解析。解析DOM文档生成一个DOM树,DOM树和解析出来的CSS样式组合生成一个渲染树,最后浏览器根据这个渲染树进行页面的排版和绘画。而最后这一步就是会涉及到Reflow和Repaint。
如下这几个行为会引发页面的Reflow或Repaint:
添加,删除,更新DOM节点
隐藏/显示DOM节点(display:none或visibility:hidden)
修改样式
改变窗口大小,滚动页面
其实浏览器在这方面已经帮咱们作了一些优化了,对于每一个触发Reflow的行为浏览器并不会立刻就触发,而是把它们保存在一个队列中,当到达必定数量的时候再进行批量的Reflow,这样就不须要每次都进行Reflow。可是,咱们的一些行为会影响到浏览器的优化,使得Reflow立刻触发。当咱们请求下面这些属性的时候发生这种现象:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
getComputedStyle(), or currentStyle(IE)
每当咱们请求这些属性时,浏览器为了返回实时的状况就必须立刻进行Reflow以计算出咱们所须要的属性。因此咱们应该尽可能少的使用这些属性。
从上面能够发现,基于全部DOM操做都会引发Reflow或Repaint,因此尽量避免页面的Reflow或Repaint能够很好的提升DOM性能。那么该怎么作才能最好的避免或最小化Reflow呢?下面有几个有用的建议:
1.不要逐一修改样式,而改成经过修改className来批量改变样式,若是样式须要动态计算,那么也要使用cssText属性来批量添加样式。例如:
// 错误的作法 var left = 10, top = 10; el.style.left = left + "px"; el.style.top = top + "px"; // 使用修改className来进行优化 el.className += " theclassname"; // 若是须要动态修改css,那么就使用cssText el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
2.批量处理DOM操做而且让元素脱离文档流,等操做结束后再放回文档流中。有如下几种办法:
使用display:none隐藏element,而后进行操做,最后再显示出来
使用documentFragment ,把新增的节点放在documentFragment中,最后再把documentFragment放到DOM中,由于把documentFragment放到DOM中,它只会把它的孩子节点放到DOM中,就好像documentFragment不存在。
经过cloneNode复制节点,而后离线进行操做,最后再替换DOM中的节点。
3.尽可能少的访问会引发立刻Reflow的属性,使用局部变量来缓存这些属性,好比:
var left = el.offsetLeft, top = el.offsetTop esty = el.style; for(big; loop; here) { left += 10; top += 10; esty.left = left + "px"; esty.top = top + "px"; }
4.对于须要动画的元素,尽可能让它脱离文档流,这样就能尽可能引发尽可能小的Reflow
5.尽可能少使用table布局
事件代理我想这个你们应该都知道了。越多的事件绑定页面就加载越慢而且占用更多内存,同时绑定太多事件也会使得代码的可读性下降。使用事件代理的方法原理就是把事件绑定到元素的父节点,而后在处理函数中判断target,根据不一样的target执行不一样的逻辑。这样能很大程度的减小绑定是事件数量而且提升代码的简洁度。
看了这么多其实总结起来仍是比较简单的,在进行DOM操做的时候尽可能把DOM操做转换为本地的Javascript操做,使用时先缓存一些DOM元素或者属性,缓存长度。在须要进行大量DOM操做的时候,先让元素脱离文档,等操做结束再把元素放回文档中。优化策略仍是须要在实践中不断尝试,不断摸索,找出最优的解决方案。
最近准备毕设没什么时间更新博客,后面尽可能安排好时间作到一周一篇,前端优化Javascript篇未完待续。。。
原文地址:
http://lakb248.github.io/2014/06/13/optimization_of_front-end--javascript(4optimization_of_dom)/