JavaScript学习--DOM编程、回流重绘对性能的影响

DOM编程

文档对象模型(DOM)是一个和语言无关的,用于操作XML和HTML应用程序API。DOM API主要用来访问文档中的数据

浏览器通常会把DOM和JavaScript独立实现,也就是说DOM和JavaScript在不同的文件中实现。这样的分离利于其他语言共享使用DOM及其相关函数。Chrome使用Webkit中的WebCore库来渲染页面,JavaScript引擎是V8。

实际上DOM的修改和访问本来就很慢,前面说过DOM和JavaScipt在不同的文件中实现,那么当JavaScipt调用DOM的API的时候必定会有代价。访问DOM的次数越多,代价越大。修改元素的代价更大,因为它会导致浏览器重新计算页面的几何变化,所以尽量减少访问DOM的次数。

如果在一个对性能有着苛刻要求的操作中更新一大段HTML,推荐使用innerHTML,因为它在绝大部分浏览器中都运行地更快。

使用DOM方法更新页面内容的另一个途径是克隆已有元素,而不是创建新元素。即element.cloneNode();

var ele = document.createElement('a');
var ele_temp = ele.cloneNode();
HTML集合

HTML集合是包含了DOM节点引用的类数组对象。以下方法的返回值就是一个集合:
document.getElementsByName();
document.getElementsByClassName();
document.getElementsByTagName();
下面的属性同样返回HTML集合
document.images 页面中所有img元素
document.links 所有a元素
document.forms 所有表单元素
document.=forms[0].elements

注意,HTML集合放回的是类似数组的列表,不是真正的数组,也就是说集合的数据结构包含一个length属性,并且还能以数字索引的方式访问列表中的元素。

注意,HTML集合处于“实时状态”实时存在,这意味着当底层文档对象刷新时候,集合也会自动更新,所以,一旦在JavaScript中将HTML集合赋值给某个变量,该变量也会随着改变。所以下面的代码会出现死循环。(alldivs.length不断增大)

    var alldivs = document.getElementsByTagName('div');
    for (var i = 0; i < alldivs.length; ++i) {
        document.body.appendChild(document.createElement('div'));
    }

为了解决这个问题,可以将集合转化为数组,代码如下:

function toArray(coll) {
	for (let i = 0,a = [],len = coll.length; i < len; ++i) {
		a[i] = coll[i];
	}
	return a;
} 

当遍历一个集合时,首要优化原则是集合存储在局部变量中,并把length缓存在循环外部,然后,使用局部变量访问这些需要多次访问的元素。

通常你需要从某一个DOM元素开始,操作周围的元素,或者递归查找所有子节点。你可以使用childNodes得到元素集合,或者用nextSibling来获取每个相邻元素。

    function testNextSibling() {
        var el = document.getElementById('mydiv'),
        ch = el.firstChild,
        name = '';
        do {
            name = ch.nodeName;
        } while(ch = ch.nextSibling);
        return name;
    }
    function testChildNodes() {
        var el = document.getElementById('mydiv'),
        ch = el.childNodes,
        len = ch.length,
        name = '';
        for (var count = 0;count < len; ++count) {
            name = ch[count].nodeName;
        }
        return name;
    }

选择器API querySelectorAll()方法使用CSS选择器作为参数并返回一个NodeList-包含着匹配节点的类数组对象。这个方法不会返回HTML集合,因此返回的节点不会对应实时的文档结构,避免了前面提到的可能导致死循环等问题。

var element = document.querySelectorAll('#menu a');
浏览器渲染HTML的步骤

HTML渲染大致分为如下几步:
1、HTML被HTML解析器解析成DOM Tree, css则被css解析器解析成CSSOM Tree。
2、DOM Tree和CSSOM Tree解析完成后,被附加到一起,形成渲染树(Render Tree)。
3、节点信息计算(重排),这个过程被叫做Layout(Webkit)或者Reflow(Mozilla)。即根据渲染树计算每个节点的几何信息。
4、渲染绘制(重绘),这个过程被叫做(Painting 或者 Repaint)。即根据计算好的信息绘制整个页面。


  • 浏览器使用流式布局模型(Flow Based Layout)。
  • 由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table等及其内部元素除外,他们可能需要多次计算,通常要花3倍同等元素的事件,这也是为什么要避免使用table布局的原因之一。
回流和重绘

当DOM的变化影响了元素的集合属性(宽和高)——比如改变边框宽度或给段落增加文字等,浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此收到影响。浏览器会使渲染树中收到影响的部分失效,并重新构造渲染树。这个过程称为重排/回流(reflow)。完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘(repaint)。

回流必定会导致重绘,而重绘不一定会引起回流。例如改变一个元素的背景色并不会改变元素的布局,所以只发生了重绘。

回流和重绘都是代价比较昂贵的操作,它们会导致web应用程序的UI反应迟钝。

一些常用且会导致回流的情况:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如: :hover)
  • 查询某些属性或调用某些方法

浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者事件间隔达到一个阈值,浏览器就会把队列清空,进行一次批处理,这样就可以把多次回流和重绘变成一次。
获取布局信息的操作会导致列队刷新,比如以下方法:

    offsetTop, offsetLeft,offsetWidth,offsetHeight,
    scrollTop,scrollLeft,scrollWidth,scrollHeight,
    clientTop,clientLeft,clientWidth,clientHeight,
    width,innerHeight
    getComputedStyle()
    getBoundingClientRect()

在修改样式的过程中,最好避免使用上面列出的属性。它们都会刷新渲染队列。

最小化回流和重绘的方法
css:
  • 避免使用table布局
  • 尽可能在DOM树的最末端改变class
  • 避免设置多层内联样式
  • 将动画效果应用到position属性为absolute或fixed的元素上
  • 避免使用css表达式(例如cacl())
  • 合并多次对样式的修改,一次性处理
  • 改变css的class名称
批量修改DOM
  • 使元素脱离文档流
  • 对其应用多重改变
  • 把元素带回文档中
有三种基本方法可以使DOM脱离文档流

1、隐藏元素,应用修改,重新显示

        <ul id="mylist">
            <li><a href="#1">name1</a></li>
            <li><a href="#2">name2</a></li>
        </ul>

    function appendDataToElement(appendToElement,data) {
        var a,li;
        for (var i = 0,max = data.length; i < max; ++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.getElementById('mylist');
    ul.style.display = 'none';
    appendDataToElement(ul,data);
    ul.style.display = 'block';

2、 使用文档片段(document fragement)在当前DOM之外构建一个子树,再把它拷贝回文档。
文档片段是轻量级的document对象,它的设计初衷就是为了完成这类任务——更新和移动节点。文档片段的一个便利的语法特性是当你附加一个片段到节点中时,实际上被添加的是该片段的子节点,而不是片段本身。下面的代码少一行,只出发一次重排,而且只访问了一次实时的DOM:

    var fragment = document.createDocumentFragment();
    appendDataToElement(fragment,data);
    document.getElementById('mylist').appendChild(fragment);

3、将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素。

    var old = document.getElementById('mylist');
    var clone = old.cloneNode(true);
    appendDataToElement(clone,data);
    old.parentNode.replaceChild(clone,old);
缓存布局信息

如前文所述,浏览器尝试通过队列化修改和批量执行的方式最小化重排次数。尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后再操作局部变量。
如下代码所示,动画循环中,直接使用current变量而不是对样式做直接操作,这样可以减少查询的次数:

    current++;
    myElement.style.left = current + 'px';
    myElement.style.top = current + 'px';
    if (current >= 500) {
        stopAnimation();
    }

使用绝对位置定位页面上的动画元素,将其脱离文档流。当元素动起来。当它扩大时,会临时覆盖部分页面。但这只是页面一个小区域的重绘过程,不会产生重排并重绘页面的大部分内容。当动画结束时恢复定位,从而只会下移一次文档的其他元素。

JavaScript

  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性
  • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
  • 也可以先为元素设置display:none,等操作结束之后再把它显示出来。因为display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值