前端性能优化之 DOM 篇章

前言

前端经常需要和DOM打交道,增加、删除、替换是常见的DOM操作,然而就是这些操作时常成为网站性能的瓶颈,为了提高我们的网站性能,我们需要在DOM上面花不少心思。

1. 使用DOM的引用

在进行DOM操作前,我们先要对元素进行查找,或者需要根据后台返回的数据动态的创建元素,比如我们得到一个数组类型的数据data,需求是根据data往一个id为main的元素中添加li。我们可能会进行下面的DOM操作

for(var i = 0; i < data.length; i++ ){
  var li = document.createElement('li');
  li.innerText = data[i];
  document.getElementById('main').appendChild(li);
  }

上面的代码每次循环都会去计算一个data的length值,查询并得到main元素,可想而之其效率应该有多低下,好的方式是在进行循环之前便将data的length以及main元素缓存下来,在循环的时候使用其引用即可。

var oMain = document.getElementById('main');
var len = data.length;
var i,li;
for(i = 0; i < len; i++ ){
  li = document.createElement('li');
  li.innerText = data[i];
  oMain.appendChild(li);
  }

注意元素的查找默认根元素是document,当我们需要对某些元素进行频繁的查找的时候,可以先将某个元素缓存下来,后面的元素查找则是基于该元素,从而减短查找路径

2. 使用文档碎片

上面的操作中还有一个非常耗费性能的地方,oMain.appendChild(li),每次循环都会进行一次元素添加操作,进而导致浏览器重排,我们知道浏览器的重排和重绘是需要耗费大量的时间进行的,所以提高网页性能的一方面是考虑尽量减少重排和重绘的次数。将频繁的DOM操作先在内存中完成,最后一次性将节点推进页面当中,这里我们会用到一个方法document.createDocumentFragment,重新修改上诉例子如下

var oMain = document.getElementById('main');
var frag = document.createDocumentFragment 
var len = data.length;
var i,li;
for(i = 0; i < len; i++ ){
  li = document.createElement('li');
  li.innerText = data[i];
  frag.appendChild(li); // 所有的操作在内存中完成,这个时候不会触发重排
  }
oMain.appendChild(frag); // 最后一次性添加到页面中,只出发浏览器一次重排

3. 使用innerHTML一次性添加DOM节点

以上的代码中每次都要通过document.createElement('li')创建元素,通过li.innerText = data[i]设置文本信息,蛋疼的很,我们可以尝试着使用innerHTML来一次性添加元素,当然前提是你要先以字符串的形式把元素和数据拼接好

var oMain = document.getElementById('main');
var sHtml = '';
var len = data.length;
var i,li;
for(i = 0; i < len; i++ ){
  sHtml += '
  • '
  • + data[i] +''; } oMain.innerHTMl = sHtml; // 最后一次性添加到页面中,只出发浏览器一次重排

    虽然这样做效率有所提升,但是当页面的DOM结构一旦复杂起来,拼接字符串便会变成一件相当恶心的事情

    4. 使用事件代理批量处理事件

    事件代理本质上就是将原本自己该干的事情,委托给别人( 这里是指父节点或者祖先节点 )做。
    还是用上面的例子,假设我们现在要给每个li都添加上一个点击事件,可能会写出下面的代码

    var oMain = document.getElementById('main');
    var frag = document.createDocumentFragment 
    var len = data.length;
    var i,li;
    for(i = 0; i < len; i++ ){
      li = document.createElement('li');
      li.innerText = data[i];
      li.addEventListener('click', function(){
        // do something
      },false)
      frag.appendChild(li); 
      }
    oMain.appendChild(frag);

    很显然data的lenth有多大,循环就得执行多少次addEventListener这个函数多少次,当数据量很大的时候效率自然低了不少,所以我们可以尝试使用事件代理的形式将事件委托到main元素上

    var oMain = document.getElementById('main');
    var sHtml = '';
    var len = data.length;
    var i,li,target;
    for(i = 0; i < len; i++ ){
      sHtml += '
  • '
  • + data[i] +''; } oMain.innerHTMl = sHtml; // 最后一次性添加到页面中,只出发浏览器一次重排 oMain.addEventListener('click', function(ev){ target = ev.target || ev.srcElement; if(target.tagName.toLowerCase() == 'li'){ // do something } }, false)

    5. 通过className来批量修改元素样式

    经常有这样的场景,我们需要在js中批量的修改元素的样式,比如

    ele.style.width = 100 + 'px';
    ele.style.height = 100 + 'px';
    ele.style.backgrounfColor = 'red';
    ele.style.border = 'solid 1px green';

    以上代码会多次出发浏览器重绘和重排,一种好的方式是将需要修改的样式在样式文件中先写好,通过给元素赋值className的形式批量修改样式

    .active{
      width: 100px;
      height: 100px;
      backgroung-color: red;
      border: solid 1px green;
      }

    给元素赋值className

    ele.className += ' active'; // 注意前面的空格