配色: 字号:
关于DOM的操作以及性能优化问题
2017-01-07 | 阅:  转:  |  分享 
  
关于DOM的操作以及性能优化问题



大家都知道DOM的操作很昂贵。



然后贵在什么地方呢?



一、访问DOM元素



二、修改DOM引起的重绘重排



一、访问DOM



像书上的比喻:把DOM和JavaScript(这里指ECMScript)各自想象为一个岛屿,它们之间用收费桥梁连接,ECMAScript每次访问DOM,都要途径这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高。因此,推荐的做法是尽量减少过桥的次数,努力待在ECMAScript岛上。我们不可能不用DOM的接口,那么,怎样才能提高程序的效率?



既然无法避免,那就减少访问。(width、offsetTop、left。。。能少就少,可以缓存起来的,就缓存)

复制代码

//code1错误

console.time(1);

for(vari=0;i
document.getElementById(''div1'').innerHTML+=''a'';

}

console.timeEnd(1);



//code2正确

console.time(2);

varstr='''';

for(vari=0;i
str+=''a'';

}

document.getElementById(''div2'').innerHTML=str;

console.timeEnd(2);

////////////////////////



复制代码

html集合&遍历DOM

html集合类似数组,但是跟数组还是不一样的。如:document.getElementsByTagName(''a'')返回的html集合。这个集合是实时更新的,即后面代码修改了DOM,会反映在这个html集合里面。可尝试代码。



复制代码





  • apple


  • orange


  • banana








  • varlis=document.getElementsByTagName(''li'');

    varpeach=document.createElement(''li'');

    peach.innewww.tt951.comrHTML=''peach'';

    document.getElementById(''fruit'').appendChild(peach);



    console.log(lis.length);//4



    复制代码





    正因为这个原因:html集合,读取length属性比数组消耗大多了。



    要解决这个问题并不难,在遍历DOM集合的时候,缓存length就好了。不要每次使用就获取,主要体现在for循环中(你应该知道,for循环中,每一次都会执行判读语句,读取length)



    复制代码

    console.time(0);

    varlis0=document.getElementsByTagName(''li'');

    varstr0='''';

    for(vari=0;i
    str0+=lis0[i].innerHTML;

    }

    console.timeEnd(0);



    console.time(1);

    varlis1=document.getElementsByTagName(''li'');

    varstr1='''';

    for(vari=0,len=lis1.length;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.2DOM树结构发生变化



    当DOM树的结构变化时,例如节点的增减、移动等,也会触发重排。浏览器引擎布局的过程,类似于树的前序遍历,是一个从上到下从左到右的过程。通常在这个过程中,当前元素不会再影响其前面已经遍历过的元素。所以,如果在body最前面插入一个元素,会导致整个文档的重新渲染,而在其后插入一个元素,则不会影响到前面的元素。



    2.4改变浏览器大小







    3.渲染树变化的排队和刷新



    思考下面代码:



    1varele=document.getElementById(''myDiv'');

    2ele.style.borderLeft=''1px'';

    3ele.style.borderRight=''2px'';

    4//var_top=ele.offsetTop;//刷新队列

    5ele.style.padding=''5px'';





    三行代码,三次修改元素的几何属性,浏览器应该发生三次重排重绘。



    但是浏览器并不会这么笨,它也是有做优化的。它会把三次修改“保存”起来(大多数浏览器通过队列化修改并批量执行来优化重排过程,也有设置时间片段的),一次完成!



    然而,如果你在三行代码中,以下获取DOM布局信息。(为了返回最新的布局信息,将立即执行渲染树变化队列的更新)



    如上面被注释的第4行,如果取消注释会导致(2+3)、(5)两次重排;



    获取关于DOM布局信息的属性:



    offsetTop,offsetLeft,offsetWidth,offsetHeight

    scrollTop,scrollLeft,scrollWidth,scrollHeight

    clientTop,clientLeft,clientWidth,clientHeight

    getComputedStyle()(currenwww.baiyuewang.nettStyleinIE)





    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片段,再一次性添加到文档中去,而不是循环添加每一行。



    复制代码

    varfragment=document.createDocumentFragment();//未使用的虚拟节点,appendChild(fragment)//append的是里面的子元素



    varli=document.createElement(''li'');

    li.innerHTML=''apple'';

    fragment.appendChild(li);



    varli=document.createElement(''li'');

    li.innerHTML=''watermelon'';

    fragment.appendChild(li);



    document.getElementById(''fruit'').appendChild(fragment);



    献花(0)
    +1
    (本文系thedust79首藏)