前端性能优化

GimmeS 2019-12-15

前言

性能优化,一直作为前端的一个热点问题,作为一个优秀的前端开发人员,性能优化时必备技能。本文将从减少http请求次数、减少单次请求资源大小、渲染优化、资源加载优化等四个大方向,下分诸多小方向,全面总结常用前端优化方法。
(内容较多请看目录)

减少http请求次数

1.浏览器缓存策略

浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:

  • Memory Cache:是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。浏览器秉承的是“节约原则”,我们发现,Base64格式的图片,几乎永远可以被塞进memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的JS、CSS文件,也有较大地被写入内存的几率——相比之下,较大的JS、CSS文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。
  • Service Worker Cache:是一种独立于主线程之外的Javascript线程。它脱离于浏览器窗体,因此无法直接访问DOM。这样独立的个性使得 Service Worker的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为Service Worker Cache。
  • HTTP Cache:它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

      强缓存是利用http头中的Expires和Cache-Control两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expires 和 cache-control判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信。

      协商缓存依赖于服务端与浏览器之间的通信。协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是304。
  • Push Cache:是指 HTTP2 在 server push 阶段存在的缓存。Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。

      Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。

    2.CDN

    CDN 的核心点有两个,一个是缓存,一个是回源。

      “缓存”就是说我们把资源 copy一份到CDN服务器上这个过程,“回源”就是说CDN发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程。

      CDN往往被用来存放静态资源。所谓“静态资源”,就是像 JS、CSS、图片等不需要业务服务器进行计算即得的资源。用户可以从一个较优的服务器获取数据,从而达到快速访问,并减少源站负载压力的目的。

      另外,CDN的域名必须和主业务服务器的域名不一样,要不,同一个域名下面的Cookie各处跑,浪费了性能流量的开销,CDN域名放在不同的域名下,可以完美地避免了不必要的 Cookie 的出现!

    3.图片处理

  • sprite雪碧图:CSS雪碧图是以前非常流行的技术,把网站上的一些图片整合到一张单独的图片中,可以减少网站的HTTP请求数量,但是当整合图片比较大时,一次加载比较慢。随着字体图片、SVG图片的流行,该技术渐渐退出了历史舞台。
  • base64图片编码:将图片的内容以Base64格式内嵌到HTML中,可以减少HTTP请求数量。但是,由于Base64编码用8位字符表示信息中的6个位,所以编码后大小大约比原始值扩大了 33%
  • 字体图标:字体图标通过自己规定字体的unicode编码,找到文件后根据unicode码去查找绘制外形,以文字的形式代替图片。
  • 4.文件合并

    将公共的js、css样式合并为一个大文件。

    根据不同页面的需求单独合并所需js、css文件。

5.减少重定向

尽量避免使用重定向,当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载,降低了用户体验。

  如果一定要使用重定向,如http重定向到https,要使用301永久重定向,而不是302临时重定向。因为,如果使用302,则每一次访问http,都会被重定向到https的页面。而永久重定向,在第一次从http重定向到https之后,每次访问http,会直接返回https的页面。

减少单次请求资源大小

6.css压缩、图片压缩、gzip压缩、js混淆等

css压缩,就是进行简单的压缩,压缩空白等。

图片压缩,主要也是减小体积,在不影响观感的前提下,可以删除一些无关紧要的色彩。另外可以使用webp格式图片。

gzip压缩主要是针对html文件来说的,它可以将html中重复的部分进行一个打包,多次复用。

js混淆可以有简单的压缩(将空白字符删除)、丑化(将一些变量缩小)、或者对js进行混淆加密。

渲染优化

7.优化css选择符

CSS 选择符是从右到左进行匹配的,比如 #myul li {}实际开销相当高。因此需要对选择符进行优化,主要有如下几方面:

  • 避免使用通配符*,只对需要用到的元素进行选择。
  • 少用标签选择器。如果可以,用类选择器替代。错误:#dataList li{} 正确:.dataList{}
  • 关注可以通过继承实现的属性,避免重复匹配重复定义。
  • 不要画蛇添足,id 和 class 选择器不应该被多余的标签选择器拖后腿。错误:.dataList#title 正确:#title
  • 减少嵌套。后代选择器的开销是最高的,因此我们应该尽量将选择器的深度降到最低(最高不要超过三层),尽可能使用类来关联每一个标签元素。

    8.减少回流和重绘次数

  • 回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。
  • 重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。

重绘不一定导致回流,回流一定会导致重绘。

9.减少DOM操作

从上面可以知道,DOM改变容易引起回流和重绘,因此我们要减少DOM操作。
例子剖析,如下代码:

for(var count=0;count<10000;count++){ 
  document.getElementById('container').innerHTML+='<span>我是一个小测试</span>'  //我们每一次循环都调用 DOM 接口重新获取了一次 container 元素,额外开销
}

进化一

// 只获取一次container
let container = document.getElementById('container')
for(let count=0;count<10000;count++){ 
  container.innerHTML += '<span>我是一个小测试</span>'
}

进化二

考虑JS 的运行速度,比 DOM 快得多这个特性。我们减少 DOM 操作的核心思路,就是让 JS 去给 DOM 分压。

//减少不必要的DOM更改
let container = document.getElementById('container')
let content = ''
for(let count=0;count<10000;count++){ 
  // 先对内容进行操作
  content += '<span>我是一个小测试</span>'
} 
// 内容处理好了,最后再触发DOM的更改
container.innerHTML = content

进化三

在 DOM Fragment 中,DocumentFragment 接口表示一个没有父级文件的最小文档对象。它被当做一个轻量版的 Document 使用,用于存储已排好版的或尚未打理好格式的XML片段。因为 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会引起 DOM 树的重新渲染的操作(reflow),且不会导致性能等问题。

let container = document.getElementById('container')
// 创建一个DOM Fragment对象作为容器
let content = document.createDocumentFragment()
for(let count=0;count<10000;count++){
  // span此时可以通过DOM API去创建
  let oSpan = document.createElement("span")
  oSpan.innerHTML = '我是一个小测试'
  // 像操作真实DOM一样操作DOM Fragment对象
  content.appendChild(oSpan)
}
// 内容处理好了,最后再触发真实DOM的更改
container.appendChild(content)

进化四

当涉及到过万调数据进行渲染,而且要求不卡住画面,如何解决?
如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来每 16 ms 刷新一次。

setTimeout(() => {
        // 插入十万条数据
        const total = 100000
        // 一次插入 20 条,如果觉得性能不好就减少
        const once = 20
        // 渲染数据总共需要几次
        const loopCount = total / once
        let countOfRender = 0
        let ul = document.querySelector('ul')
        function add() {
          // 优化性能,插入不会造成回流
          const fragment = document.createDocumentFragment()
          for (let i = 0; i < once; i++) {
            const li = document.createElement('li')
            li.innerText = Math.floor(Math.random() * total)
            fragment.appendChild(li)
          }
          ul.appendChild(fragment)
          countOfRender += 1
          loop()
        }
        function loop() {
          if (countOfRender < loopCount) {
            window.requestAnimationFrame(add)
          }
        }
        loop()
      }, 0)

10.使用事件委托

事件委托是指将事件监听器注册在父级元素上,由于子元素的事件会通过事件冒泡的方式向上传播到父节点,因此,可以由父节点的监听函数统一处理多个子元素的事件。利用事件委托,可以减少内存使用,提高性能及降低代码复杂度。

11.节流和防抖

当用户进行滚动,触发scroll事件,用户的每一次滚动都将触发我们的监听函数。函数执行是吃性能的,频繁地响应某个事件将造成大量不必要的页面计算。因此,我们需要针对那些有可能被频繁触发的事件作进一步地优化。节流与防抖就很有必要了!

  • 函数节流: 频繁触发,但只在特定的时间内才执行一次代码
  • 函数防抖: 频繁触发,但只在特定的时间内没有触发执行条件才执行一次代码
    //防抖函数
//节流函数
function throttle(fn,time){
    var last = 0;
    return function(){
        var context = this;
        var now = Date.now();
        if (now - last >= time){
            fn.apply(this, arguments);
            last = now;
        }
    };
}
//防抖函数
function debounce(fn, time){
    return function(){
        var context = this;
        clearTimeout(timeId);
        timeId = setTimeout(function(){
            fn.apply(context, arguements);
        }, time);
    };
}

资源加载优化

12.资源预加载和懒加载

  • 懒加载会延迟加载资源或符合某些条件时才加载某些资源。
  • 预加载是提前加载用户所需的资源,加速页面的加载速度,保证良好的用户体验。

资源懒加载和资源预加载都是一种错峰操作,在浏览器忙碌的时候不做操作,浏览器空间时,再加载资源,优化了网络性能。

13.css、js文件引用位置优化

  • CSS文件放在head中,先外链,后本页。
  • JS文件放在body底部,先外链,后本页。
  • body中间尽量不写style标签和script标签。
  • 处理页面、处理页面布局的JS文件放在head中,如babel-polyfill.js文件、flexible.js文件。

相关推荐