写在前面:javascript
你们都知道DOM的操做很昂贵。 css
而后贵在什么地方呢? html
1、访问DOM元素前端
2、修改DOM引发的重绘重排java
1、访问DOM 数组
像书上的比喻:把DOM和JavaScript(这里指ECMScript)各自想象为一个岛屿,它们之间用收费桥梁链接,ECMAScript每次访问DOM,都要途径这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高。所以,推荐的作法是尽可能减小过桥的次数,努力待在ECMAScript岛上。咱们不可能不用DOM的接口,那么,怎样才能提升程序的效率?浏览器
// 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集合相似数组,可是跟数组仍是不同的。如: 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)app
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);
2、重绘重排
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.渲染树变化的排队和刷新
思考下面代码:
1 var ele = document.getElementById('myDiv'); 2 ele.style.borderLeft = '1px'; 3 ele.style.borderRight = '2px'; 4 // var _top = ele.offsetTop; //刷新队列 5 ele.style.padding = '5px';
三行代码,三次修改元素的几何属性,浏览器应该发生三次重排重绘。
可是浏览器并不会这么笨,它也是有作优化的。它会把三次修改“保存”起来(大多数浏览器经过队列化修改并批量执行来优化重排过程,也有设置时间片断的),一次完成!
然而,若是你在三行代码中,如下获取DOM布局信息。(为了返回最新的布局信息,将当即执行渲染树变化队列的更新)
如上面被注释的第4行,若是取消注释会致使(2+3)、(5)两次重排;
获取关于DOM布局信息的属性:
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);
参考文档: