Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端性能优化之DOM篇章 #32

Open
qianlongo opened this issue Aug 19, 2018 · 0 comments
Open

前端性能优化之DOM篇章 #32

qianlongo opened this issue Aug 19, 2018 · 0 comments
Labels

Comments

@qianlongo
Copy link
Owner

前言

前端经常需要和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 += '<li>'+ data[i] +'</li>';
}

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 += '<li>'+ data[i] +'</li>';
}

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'; // 注意前面的空格
@qianlongo qianlongo added the JS label Aug 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant