运行时性能优化的7条规则

119 阅读6分钟

背景

为什么要做前端的性能优化?网站的性能对于用户的留存率、转化率有很大的影响,且非常直接的说提高网站性能就是在提高收入

规则

1、减少重绘重排

浏览器渲染过程:

  • 解析HTML生成DOM树
  • 解析CSS生成CSSOM规则树
  • 将DOM树与CSSOM规则树合并在一起生成渲染树
  • 遍历渲染树开始布局,计算每个节点的位置大小信息
  • 将渲染树每个节点绘制到屏幕
11111.png

重排:当改变DOM元素位置或者大小时,会导致浏览器重新生成渲染树,这个过程就是重排

重绘:当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程就叫重绘

重排会导致重绘,但是重绘不会导致重排,比如改变字体颜色只会导致重绘

导致重排的操作有:

  • 添加或删除可见的DOM元素

  • 元素位置改变

  • 元素尺寸改变

  • 内容改变

  • 浏览器窗口尺寸改变

如何减少重排重绘?

  • 用JS修改样式时,最好不用直接写样式,而是替换class来改变样式
  • 如果要对DOM元素执行一系列操作,可以将DOM元素脱离文档流,修改完成后,再将它带回文档。(比如使用隐藏元素display:none或者文档碎片DocumentFragement)

-隐藏元素可以通过v-show,因为v-show的实质就是设置css中的display为none

-文档碎片DocumentFragement是什么

场景:比如要在一个ul中添加100个li,如果不使用文档碎片,就需要使用append100次追加,会导致浏览器一直不停地渲染,非常消耗资源,

但是使用文档碎片,就可以先将100个li添加到文档碎片中,然后直接把文档碎片追加到ul。所以文档碎片其实就是一个临时仓库。

//先创建文档碎片 
let oFragmeng = document.createDocumentFragment(); 
let oUl = document.createElement("ul"); 

for(var i=0;i<10000;i++) { 
    var op = document.createElement("li"); 
    var oText = document.createTextNode(i); 
    op.appendChild(oText); 
    //先附加在文档碎片中 
    oFragmeng.appendChild(op); 
} 

//最后一次性添加到document中 
oUl.appendChild(oFragmeng);

2、使用事件委托

事件委托利用到了事件冒泡,使用事件委托可以节省内存

<ul>   
    <li>苹果</li>   
    <li>香蕉</li>   
    <li>凤梨</li> 
</ul>  

// 使用了事件委托,在父元素中添加点击事件,点击子元素li时就会发生冒泡,传到父元素上 document.querySelector('ul').onclick = (event) => {   
      const target = event.target   
      if (target.nodeName === 'li') {     
           console.log(target.innerHTML)   
      } 
}  

// 没有使用事件委托,将会占用大量内存,影响网页的性能 document.querySelectorAll('li').forEach((e) => {   
        e.onclick = function() {     
             console.log(this.innerHTML)   
        } 
}) 

3、if-else对比switch

当判断条件数量越来越多时,越倾向于使用switch

如果要判断color的值,通过if-else语句则要判断多次,直到结果为true

通过switch判断的话只需要进行一次判断,并且可读性更好

if (color == 'blue') { 

  } else if (color == 'yellow') { 

  } else if (color == 'white') {  
  
  } else if (color == 'black') {
  
  } else if (color == 'green') {
  
  } else if (color == 'orange') { 
  
  } else if (color == 'pink') { 
  
}  

switch (color) {     
    case 'blue':          
       break     
    case 'yellow':          
       break     
    case 'white':          
       break     
    case 'black':          
       break     
    case 'green':          
       break     
    case 'orange':          
       break     
    case 'pink':        
       break 
}

4、查找表

当条件语句特别多时,不妨可以试一下查找表,查找表可以使用数组和对象来进行构建

根据数字来查找,获得对应结果  
// 数组 
const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]  

return results[index]  

根据字符串来查找,获得对应结果 
const map = {   
     red: result0,   
     green: result1, 
}  
return map[color]

5、使用 Web Workers (不是每个浏览器都支持这个特性的)

Web Worker使用其他工作线程从而独立于主线程之外,它可以执行任务而不干扰用户界面。

使用 Web Worker 也非常简单,只需要预先在 Worker 中注册 message 事件,在主线程中 postMessage 给 Worker 处理就好了。处理完后可以再通过 postMessage 传结果给主线程。

适合于处理纯数据,或者是与浏览器UI无关的长时间运行脚本

在worker内不能直接操作DOM节点,也不能使用window对象的默认方法和属性

// main.js  创建一个新的worker,Worker 不能读取本地文件,所以这个脚本必须来自网络 
let myWorker = new Worker('work.js');  

first.onchange = function() { 
     // 主线程调用postMessage,向myWorker发送消息     
     myWorker.postMessage([first.value,second.value]);   
     console.log('Message posted to worker'); 
}  

// 使用onmessage以接收worker回传的信息 
myWorker.onmessage = function(e) {   
      result.textContent = e.data;   
      console.log('Message received from worker'); 
} 

// 完成任务后,主线程可以将其关掉 
worker.terminate();

// work.js  可以写一个事件处理函数代码作为响应 
onmessage = function(e) {   
      console.log('Message received from main script'); 
      // 消息本身作为事件的data属性进行使用   
      let workerResult = 'Result: ' + (e.data[0] * e.data[1]);    
      console.log('Posting message back to main script'); 
      // 对传过来的参数进行处理后可再次使用postMessage方式,将结果回传给主线程   
      postMessage(workerResult); 
}

6、使用transform和opacity属性更改来实现动画

在CSS中,transform和opacity这两个属性的更改不会触发重排和重绘

7、使用requestAnimationFrame来实现视觉变化

屏幕刷新频率即图像在屏幕上更新的速度,这个频率大概是60Hz,动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。

requestAnimationFrame是浏览器用于定时循环操作的一个接口,主要用途是按帧对网页进行重绘。设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。(由于requestAnimationFrame目前还存在兼容性问题,而且不同的浏览器还需要带不同的前缀。)

代码中使用这个API,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。 

function updateScreen(time) {   
// 视觉更新变化的操作 
}  

// updateScreen为传入的回调函数,即动画函数。首先要判断document.hidden属性是否为true,页面处于可见状态下才会执行 
requestAnimationFrame(updateScreen);

而setTimeout是通过设置一个间隔时间来不断的改变图像位置,从而达到动画效果的,但其执行的时间并不确定,就会导致它执行的步调和屏幕刷新步调不一致,出现丢帧现象。

setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像