手机版

高性能JavaScript重排和重绘(2)

时间:2021-09-08 来源:互联网 编辑:宝哥软件园 浏览:

回顾上一篇关于高性能JavaScript DOM编程的文章,提出了两个优化,一个是尽量减少DOM访问,把操作放在ECMAScript端,另一个是尽可能缓存局部变量,比如长度等。最后介绍了两个新的API,QuerySelector()和querySelectorAll(),在进行组合选择时可以大胆使用。但是本文主要讲的是DOM编程最耗时的地方,重排和重绘。

1.什么是重排和重绘?下载完页面中的所有组件——HTML标签、JavaScript、CSS和图片后,浏览器会解析生成两个内部数据结构——DOM树和渲染树。

DOM树表示页面结构,渲染树表示DOM节点的显示方式。DOM树中每个需要显示的节点在渲染树中至少有一个对应的节点(disply值为none的隐藏DOM元素,渲染树中没有对应的节点)。渲染树中的节点称为“框架”或“框”,符合CSS模型的定义,理解页面元素是一个有填充、边距、边框和位置的框。一旦建立了DOM和渲染树,浏览器就开始显示(绘制)页面元素。

当DOM的变化影响到元素的几何属性(宽度或高度)时,浏览器需要重新计算元素的几何属性,其他元素的几何属性和位置也会受到影响。浏览器会使渲染树的受影响部分无效,并重建渲染树。这个过程叫做重排。重排后,浏览器将受影响的部分重绘到屏幕上,这称为重绘。由于浏览器的流布局,渲染树的计算通常只需要遍历一次。除了table及其内部元素之外,确定其在渲染树中节点的属性可能需要进行许多计算,这通常需要相当于等效元素三倍的时间。这也是我们应该避免使用表格进行布局的原因之一。

并非所有的DOM更改都会影响几何属性。例如,更改元素的背景颜色不会影响元素的宽度和高度,在这种情况下,只会发生重绘。

2.重排和重绘的代价是什么?让我们回到前面过桥的例子。如果你细心一点,你可能会发现,成千上万次的时差,并不是“过桥”造成的。每次“过桥”其实都伴随着强调和重画,大部分的精力消耗都在这里!

var次数=15000;//code1重绘console.time(1)每次过桥;for(var I=0;I倍;I){ document . getelementbyid(' mydiv 1 ')。innerHTML=' a} console . timeend(1);//code2只跨桥控制台. time(2);var str=for(var I=0;I倍;I){ var tmp=document . getelementbyid(' mydiv 2 ')。innerHTMLstr=' a} document . getelementbyid(' mydiv 2 ')。innerHTML=strconsole . timeend(2);//code 3 console . time(3);var _ str=for(var I=0;I倍;I){ _ str=' a ';} document . getelementbyid(' mydiv 3 ')。innerHTML=_ strconsole . timeend(3);//1: 2874.619 ms//2: 11.154 ms//: 1.282 ms数据不能说谎。看,多次访问DOM对于重排和重绘来说是非常耗时的。

3.重排何时发生显然,每一次重排都会不可避免地导致重绘。那么,在什么情况下会发生重排呢?

1.添加或删除可见的DOM元素。2.更改元素的位置。3.更改元素的大小。4.更改元素的内容(例如,一个文本被另一个不同大小的图片替换)。5.页面渲染的初始化(这是不可避免的)。6.改变浏览器窗口的大小是显而易见的。也许你有过这样的经历,不断改变浏览器窗口的大小会导致UI响应变慢(甚至在IE的一些较低版本中直接挂机)

4.渲染树更改的排队和刷新请考虑以下代码:

var ele=document . getelementbyid(' myDiv ');ele . style . bordereft=' 1px ';ele . style . borderright=' 2px ';ele . style . padding=' 5px ';一开始以为一个元素的样式改变了三次,每次改变都会引起重排和重绘,所以总共有三次重排和重绘过程,但是浏览器不会那么笨,会把这三次修改“保存”下来(大部分浏览器都是通过队列修改和批量执行来优化重排过程),完成一次!但是,有时您可能(经常是无意识地)强制刷新队列,并要求立即执行计划的任务。获取布局信息的操作会导致队列刷新,例如:

1.offsetTop,offsetLeft,offsetWidth,offset light 2 . scroll top,scrollLeft,scrollWidth,scrollHeight3.clientTop,clientLeft,clientWidth,Clientheight4 .getcomputed style()(ie中的currentstyle)稍微修改了上面的代码:

var ele=document . getelementbyid(' myDiv ');ele . style . bordereft=' 1px ';ele . style . borderright=' 2px ';//这里使用offset heart//.ele . style . padding=' 5px ';由于offset heat属性需要返回最新的布局信息,浏览器必须执行渲染队列中的“挂起的更改”并触发重排才能返回正确的值(即使队列中更改的样式属性与要获取的属性值无关),因此上面代码的前两个操作将缓存在渲染队列中进行处理,但一旦请求offset heat属性,队列将立即执行,因此总共有两次重排和重绘。所以布局信息发生变化时尽量不要查询。

5.最小化重排和重绘我们仍然看上面的代码:

var ele=document . getelementbyid(' myDiv ');ele . style . bordereft=' 1px ';ele . style . borderright=' 2px ';ele . style . padding=' 5px ';这三种样式属性会发生变化,每一种都会影响元素的几何结构。虽然大多数现代浏览器已经对其进行了优化,并且只导致了一次重新排列,但如上所述,如果请求了一个及时的属性,队列将被强制刷新,这段代码将访问DOM四次。一个显而易见的优化策略是将它们的操作合成一次,这样只会修改DOM一次:

var ele=document . getelementbyid(' myDiv ');//1.重写style . style . CSS text=' border-left : 1px;border-right : 2px;padding: 5px';//2.添加style ele . style . CSS text=' border-;' eft: 1px'//3.使用classele.className=' active6.片段元素的应用程序查看以下代码,并考虑一个问题:

Ul id='水果' Li Li如果您想在代码中添加两个选项:桃子和西瓜,您会怎么做?

var lis=document.getElementById('水果');var Li=document . create element(' Li ');li.innerHTML=' applelis . appendchild(Li);var Li=document . create element(' Li ');li.innerHTML='西瓜';lis . appendchild(Li);上面的代码很容易想到,但很明显,它已经被重新排列了两次。怎么破?如前所述,隐藏元素不在渲染树中,这很好。我们可以先隐藏id为水果的ul元素(display=none),然后添加li元素,最后再次显示,但在实际操作中可能会闪烁,这很容易理解。这时,碎片元素开始发挥作用。

var fragment=document . createdocumentfragment();var Li=document . create element(' Li ');li.innerHTML=' applefragment . appendchild(Li);var Li=document . create element(' Li ');li.innerHTML='西瓜';fragment . appendchild(Li);document.getElementById('水果')。appendChild(片段);文档片段是一个轻量级的文档对象,其初衷是完成更新——、移动节点等任务。文档片段的一个方便的语法特征是,当您将片段附加到节点时,它实际上是片段的子节点,而不是片段本身。只触发了一次重排,只访问了一个实时DOM。

7.通过在动画流中展开/折叠元素来显示和隐藏一些页面是一种常见的交互模式。它通常包括展开区域的几何动画,并向下推动页面的其余部分。

一般来说,重排只影响渲染树的一小部分,但也可能影响大部分甚至整个渲染树。浏览器需要重新排列的次数越少,应用程序的响应速度就越快。因此,当页面顶部的动画移动到页面的其余部分时,会导致昂贵的大规模重排,使用户感觉页面已经吃饭了。渲染树中需要重新计算的节点越多,情况就会越糟。

使用以下步骤来避免页面中的大部分重新排列:

使用绝对位置来定位页面上的动画元素,并将它们从文档流中分离出来以使元素移动。当它展开时,会暂时覆盖一些页面。但这只是页面一小块区域的重绘过程,不会导致页面大部分内容的重排和重绘。动画结束时,定位会恢复,这样文档的其他元素只会下移一次。总结重排和重绘是造成DOM编程能耗的主要原因之一。当涉及到DOM编程时,可以参考以下几点:

当布局信息改变时,尽量不要进行查询(这会导致渲染队列的强制刷新)。同一个DOM的多个属性变化可以写在一起(减少DOM访问,降低渲染队列强制刷新为0的风险)。如果要批量添加DOM,可以让元素先离开文档流,然后在操作后再带入文档流,这样只会触发一次重排(片段元素的应用)。需要多次重新排列的元素,位置属性设置为绝对或固定,所以这个元素会例如,有动画效果的元素最好设置为绝对定位。

以上就是对高性能JavaScript的重排和重绘的介绍,可以和之前的高性能JavaScript DOM编程一起研究(1)。

希望这两篇文章能帮到你,解决你在这方面的疑惑。

版权声明:高性能JavaScript重排和重绘(2)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。