前端页面卡顿、也许是DOM操纵惹的祸?

界面上UI的变动都是经由过程DOM操纵完成的,并非经由过程传统的革新页面完成 的。只管DOM供应了雄厚接供词外部挪用,但DOM操纵的价值很高,页面前端代码的机能瓶颈也大多集合在DOM操纵上,所以前端机能优化的一个重要的关注 点就是DOM操纵的优化。DOM操纵优化的总原则是只管削减DOM操纵。

先来看看DOM操纵为何会影响机能

在浏览器中,DOM的完成和ECMAScript的完成是星散的。比方 在IE中,ECMAScrit的完成在jscript.dll中,而DOM的完成在mshtml.dll中;在Chrome中运用WebKit中的 WebCore处置惩罚DOM和衬着,但ECMAScript是在V8引擎中完成的,其他浏览器的状况类似。所以经由过程JavaScript代码挪用DOM接 口,相当于两个自力模块的交互。相比较在统一模块中的挪用,这类跨模块的挪用其机能消耗是很高的。但DOM操纵对机能影响最大实在照样由于它致使了浏览器 的重绘(repaint)和回流(reflow)。

浏览器的衬着道理

从下载文档到衬着页面的过程当中,浏览器会经由过程剖析HTML文档来构建DOM树,剖析CSS发生CSS划定规矩树。JavaScript代码在剖析过程当中, 能够会修正天生的DOM树和CSS划定规矩树。以后依据DOM树和CSS划定规矩树构建衬着树,在这个过程当中CSS会依据挑选器婚配HTML元素。衬着树包括了每 个元素的大小、边距等款式属性,衬着树中不包括隐蔽元素及head元素等不可见元素。末了浏览器依据元素的坐标和大小来盘算每一个元素的位置,并绘制这些元 素到页面上。重绘指的是页面的某些部份要从新绘制,比方色彩或背景色的修正,元素的位置和尺寸并没用转变;回流则是元素的位置或尺寸发生了转变,浏览器需 要从新盘算衬着树,致使衬着树的一部份或悉数发生变化。衬着树从新竖立后,浏览器会从新绘制页面上受影响的元素。回流的价值比重绘的价值高许多,重绘会影 响部份的元素,而回流则有能够影响悉数的元素。以下的这些DOM操纵会致使重绘或回流:

  • 增添、删除和修正可见DOM元素

  • 页面初始化的衬着

  • 挪动DOM元素

  • 修正CSS款式,转变DOM元素的尺寸

  • DOM元素内容转变,使得尺寸被撑大

  • 浏览器窗口尺寸转变

  • 浏览器窗口转动

1. 兼并屡次的DOM操纵为单次的DOM操纵

最常见频仍举行DOM操纵的是频仍修正DOM元素的款式,代码类似以下:

element.style.borderColor = '#f00';
element.style.borderStyle = 'solid';
element.style.borderWidth = '1px';

这类编码体式格局会由于频仍变动DOM元素的款式,触发页面屡次的回流或重绘,上面引见过,当代浏览器针对这类状况有机能的优化,它会兼并DOM操纵,但并非一切的浏览器都存在如许的优化。引荐的体式格局是把DOM操纵只管兼并,如上的代码能够优化为:

// 优化计划1
element.style.cssText += 'border: 1px solid #f00;';
// 优化计划2
element.className += 'empty';

示例的代码有两种优化的计划,都做到了把屡次的款式设置兼并为一次设置。计划2比计划1轻微有一些机能上的消耗,由于它须要查询CSS类。但计划2的保护性最好,这在上一章曾讨论过。许多时刻,假如机能题目并不凸起,挑选编码计划时须要优先斟酌的是代码的保护性。

类似的操纵另有经由过程innerHTML接口修正DOM元素的内容。不要直接经由过程此接口来拼接HTML代码,而是以字符串体式格局拼接好代码后,一次性赋值给DOM元素的innerHTML接口。

2. 把DOM元素离线或隐蔽后修正

把DOM元素从页面流中离开或隐蔽,如许处置惩罚后,只会在DOM元素离开和增加时,或者是隐蔽和显现时才会形成页面的重绘或回流,对离开了页面规划流的DOM元素操纵就不会致使页面的机能题目。这类体式格局合适那些须要大批量修正DOM元素的状况。详细的体式格局重要有三种:

(1)运用文档片断

文档片断是一个轻量级的document对象,并不会和特定的页面关联。经由过程在文档片断上举行DOM操纵,能够下降DOM操纵对页面机能的影响,这 种体式格局是建立一个文档片断,并在此片断上举行必要的DOM操纵,操纵完成后将它附加在页面中。对页面机能的影响只存在于末了把文档片断附加到页面的这一步 操纵上。代码类似以下:

var fragment = document.createDocumentFragment();
// 一些基于fragment的大批DOM操纵
...
document.getElementById('myElement').appendChild(fragment);
(2)经由过程设置DOM元素的display款式为none来隐蔽元素

这类体式格局是经由过程隐蔽页面的DOM元素,到达在页面中移除元素的结果,经由大批的DOM操纵后恢复元素本来的display款式。关于这类会引发页面重绘或回流的操纵,就只有隐蔽和显现DOM元素这两个步骤了。代码类似以下:

var myElement = document.getElementById('myElement');
myElement.style.display = 'none';
// 一些基于myElement的大批DOM操纵
...
myElement.style.display = 'block';
(3)克隆DOM元素到内存中

这类体式格局是把页面上的DOM元素克隆一份到内存中,然后再在内存中操纵克隆的元素,操纵完成后运用此克隆元素替代页面中本来的DOM元素。如许一来,影响机能的操纵就只是末了替代元素的这一步操纵了,在内存中操纵克隆元素不会引发页面上的机能消耗。代码类似以下:

var old = document.getElementById('myElement');
var clone = old.cloneNode(true);
// 一些基于clone的大批DOM操纵
...
old.parentNode.replaceChild(clone, old);

在当代的浏览器中,由于有了DOM操纵的优化,所以运用如上的体式格局后能够并不能显著感受到机能的改良。但是在依然占领市场的一些旧浏览器中,运用以上这三种编码体式格局则能够大幅进步页面衬着机能。

3. 设置具有动画结果的DOM元素的position属性为fixed或absolute

把页面中具有动画结果的元素设置为相对定位,使得元素离开页面规划流,从而防止了页面频仍的回流,只触及动画元素自身的回流了。这类做法能够进步动 画结果的展现机能。假如把动画元素设置为相对定位并不相符设想的请求,则能够在动画开始时将其设置为相对定位,等动画完毕后恢复原始的定位设置。在许多的 网站中,页面的顶部会有大幅的广告展现,平常会动画睁开和摺叠显现。假如不做机能的优化,这个结果的机能消耗是很显著的。运用这里提到的优化计划,则能够 进步机能。

4. 郑重获得DOM元素的规划信息

前面讨论过,猎取DOM的规划信息会有机能的消耗,所以假如存在重复挪用,最好的做法是只管把这些值缓存在局部变量中。斟酌以下的一个示例:

for (var i=0; i < len; i++) {
    myElements[i].style.top = targetElement.offsetTop + i*5 + 'px';
}

如上的代码中,会在一个轮回中重复获得一个元素的offsetTop值,事实上,在此代码中该元素的offsetTop值并不会变动,所以会存在不必要的机能消耗。优化的计划是在轮回外部获得元素的offsetTop值,相比较之前的计划,此计划只是挪用了一遍元素的offsetTop值。变动后的代码以下:

var targetTop = targetElement.offsetTop;
for (var i=0; i < len; i++) {
    myElements[i].style.top = targetTop+ i*5 + 'px';
}

别的,由于获得DOM元素的规划信息会强迫浏览器革新衬着树,而且能够会致使页面的重绘或回流,所以在有大批量DOM操纵时,应防止猎取DOM元素 的规划信息,使得浏览器针对大批量DOM操纵的优化不被损坏。假如须要这些规划信息,最好是在DOM操纵之前就获得。斟酌以下一个示例:

var newWidth = div1.offsetWidth + 10;
div1.style.width = newWidth + 'px';
var newHeight = myElement.offsetHeight + 10; // 强迫页面回流
myElement.style.height = newHeight + 'px'; // 又会回流一次

依据上面的引见,代码在碰到获得DOM元素的信息时会触发页面从新盘算衬着树,所以如上的代码会致使页面回流两次,假如把获得DOM元素的规划信息提早,由于浏览器会优化一连的DOM操纵,所以实际上只会有一次的页面回流涌现,优化后的代码以下:

var newWidth = div1.offsetWidth + 10;
var newHeight = myElement.offsetHeight + 10;

div1.style.width = newWidth + 'px';
myElement.style.height = newHeight + 'px';

5. 运用事宜托管体式格局绑定事宜

在DOM元素上绑定事宜会影响页面的机能,一方面,绑定事宜自身会占用处置惩罚时候,另一方面,浏览器保留事宜绑定,所以绑定事宜也会占用内存。页面中 元素绑定的事宜越多,占用的处置惩罚时候和内存就越大,机能也就相对越差,所以在页面中绑定的事宜越少越好。一个文雅的手腕是运用事宜托管体式格局,即应用事宜冒 泡机制,只在父元素上绑定事宜处置惩罚,用于处置惩罚一切子元素的事宜,在事宜处置惩罚函数中依据传入的参数推断事宜源元素,针对差别的源元素做差别的处置惩罚。如许就不 须要给每一个子元素都绑定事宜了,治理的事宜绑定数目变少了,天然机能也就进步了。这类体式格局也有很大的灵活性,能够很方便地增加或删除子元素,不须要斟酌因 元素移除或修改而须要修正事宜绑定。示例代码以下:

// 猎取父节点,并增加一个click事宜
document.getElementById('list').addEventListener("click",function(e) { // 搜检事宜源元素 if(e.target && e.target.nodeName.toUpperCase == "LI") { // 针对子元素的处置惩罚 ...
    }
});

上述代码中,只在父元素上绑定了click事宜,当点击子节点时,click事宜会冒泡,父节点捕捉事宜后经由过程e.target搜检事宜源元素并做响应地处置惩罚。
在JavaScript中,事宜绑定体式格局存在浏览器兼容题目,所以在许多框架中也供应了类似的接口要领用于事宜托管。比方在jQuery中能够运用以下体式格局完成事宜的托管(示例代码来自jQuery官方网站):

$( "table" ).on( "click", "td", function() { $( this ).toggleClass( "chosen" );
});
    原文作者:steven
    原文地址: https://segmentfault.com/a/1190000009619572
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞