前端性能优化综述

1,388 阅读12分钟

前端的性能优化可以从网络层面和渲染层面两个维度来实现优化,网络层面主要指的是HTTP请求与响应过程,渲染层面指的是浏览器端或者说是客户端(包括浏览器和代理服务器等)。

前端性能衡量指标

  • 白屏时间 该时间点表示浏览器开始绘制页面,在此之前页面都是白屏,也称为开始渲染时间
  • 首屏时间 该时间点表示用户看到第一屏页面的时间
  • 用户可交互时间 也叫DOM Ready,该时间点表示DOM解析完成,资源还没有完成,这个时候用户与页面可以交互了
  • 完全加载时间 该时间点是window.onload时间触发的时间,表示原始文档和所用引用的内容已经加载完成,用户最明显的感觉就是浏览器tab上loading状态结束
  • 首字节时间(TTFB) 第一字节响应时间(TTFB)=发送请求到WEB服务器的时间+WEB服务器处理请求并生成响应花费的时间+WEB服务器生成响应到浏览器花费的时间
  • DNS 解析时间
  • TCP 连接时间
  • HTTP 请求时间
  • HTTP 响应时间

前端性能优化方案

1. 构建工具性能的优化(主要是webpack的性能优化)

webpack的性能优化主要从两个方面着手,缩短构建时间和减小打包结果体积,优化方案有配置loader让它忽略掉指定文件夹的打包,将转译结果缓存、使用插件来优化(使用DllPlugin将第三方库单独打包到一个文件中;使用Happypack将loader由单进程转为多进程;使用webpack-bundle-analyzer可视化文件结构,找出导致体积过大的原因;使用DllPlugin拆分资源;使用Tree-Shaking 和UglifJsPlugin删除冗余代码;使用require.ensure按需加载)。


2. 图片的优化

图片的优化就是在做权衡,因为我们做的就是选择质量好的体积又小的图片格式,或者是压缩图片的体积又要保证图片的质量。图片优化我们需要了解不同图片格式的优缺点,它们适用的不同业务场景,不谈业务场景的选型就是流氓。一般来说,JPG适用于呈现色彩丰富的图片,比如大的背景图、轮播图或者Banner图;PNG适合用来呈现小的Logo,颜色简单且对比强烈的图片;SVG是一种更好的选择方式,它的体积更小,而且可以无限放大而不失真,但是它的渲染成本较高;Base64是作为雪碧图的补充而存在的,它不是图片格式,而是一种编码方式,我们可以直接将编码结果写入 HTML 或者写入 CSS,从而减少 HTTP 请求的次数,主要用来呈现小图,小的logo;WebP是最年轻的一种图片格式,支持有损压缩和无损压缩,集多种图片的格式的优点于一身,但是的缺点就是兼容性不好,如果要使用WebP,就要做兼容性处理。

关于图片的优化有一些建议:

  • CSS Sprites利用CSS background相关元素进行背景图绝对定位,把多个图片合成一个图片
  • 使用 image 和 map 标签构成图片地图,可以显著减少 HTTP 请求数量,不需要为每个链接添加一个背景图片。但是这个应用好像很少见到。
  • 使用data:url的模式来内联图片,图片信息就在url中,可以随文档一起传输减少了HTTP请求。

3. 压缩

HTTP压缩就是以缩小体积为目的 ,对HTTP内容进行重新编码的过程。Gzip 压缩背后的原理,是在一个文本文件中找出一些重复出现的字符串、临时替换它们,从而使整个文件变小。根据这个原理,文件中代码的重复率越高,那么压缩的效率就越高,使用 Gzip 的收益也就越大。我们知道服务端的Gzip压缩是消耗CPU的,所以我们需要给服务器分压,我们可以利用webpack中的gzip压缩来为服务端的gzip分压。


4. 浏览器缓存

缓存可以减小网络IO消耗,提高访问速度,一般我们讲的浏览器可以简单理解为HTTP缓存,但是浏览器的缓存包括四个方面,首先是内存(Memory Cache),然后是Service Worker Cache,再是磁盘(Disk Cache,也就是HTTP Cache),最后是Push Cache。


5. 本地存储

本地存储主要是Cookie,Web Storage(Local Storage和Session Storage)和IndexDB。Cookie可以用来存储用户爱好和购买记录,用户浏览过的网站,曾经点击过的广告等信息;Session Storage 更适合用来存储生命周期和它同步的会话级别的信息。这些信息只适用于当前会话,当你开启新的会话时,它也需要相应的更新或释放,比如微博的 Session Storage 就主要是存储你本次会话的浏览足迹;Local Storage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串;IndexDB 是一个运行在浏览器上的非关系型数据库,IndexDB 可以看做是 LocalStorage 的一个升级,当数据的复杂度和规模上升到了 LocalStorage 无法解决的程度,我们毫无疑问可以请出 IndexDB 来帮忙。


6. CDN存储优化

CDN的核心点有两个,缓存和回源,CDN往往用来存放静态资源,它像一个仓库,而根服务器本质上是业务服务器,它的任务是生成动态页面或返回非纯静态页面,这两种过程都是需要计算的。CSD的效用最大化涉及到服务器本身的性能优化,CDN节点的选址等,另外在CDN的域名选取时要与业务服务器的域名不一样,这样小的一个细节也能很大的提高性能。


7. 服务端渲染

服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。很多网站是为了让搜索引擎搜到网站内容才采取服务端渲染的,性能倒是其次,而服务端渲染本身解决了一个非常关键的性能问题——首屏加载速度过慢。在react,vue中服务端渲染的实践中,主要在于两点:一是renderToString()方法,而是把转化结果塞进模板里。SSR优化的再好还是比较消耗CPU的,我们何不要让服务器去做这件事,一个公司服务器是有限的,公司更愿意让用户的浏览器去跑,所以大部分网站都没有用服务端渲染,另外首屏渲染体验和SEO的优化方案有很多,一般我们都不会取用服务端渲染。


8. CSS性能方案

  • 避免使用通配符,只对需要用到的元素进行选择
  • 关注可以通过继承实现的属性,避免重复匹配重复定义
  • 少用标签选择器,如果可以,用类选择器替代
  • 不要画蛇添足 ,id和class选择器不应该被多余的标签选择器拖后腿
  • 减少嵌套

9. JS性能方案


10. DOM优化

DOM的访问和修改所带来的开销都是很大的,而且对DOM的修改会引发它的外观(样式)的改变时,就会触发回流或重绘,其实是DOM的修改触发了渲染树(render tree)的变化,然后导致回流或重绘。DOM的优化主要就是减少对DOM的访问(少交过路费)和减少对DOM的修改(避免过度渲染,避免过度回流或重绘),我们经常做的就是让JS给DOM分压用DOM Fragment来缓存批量化的DOM操作

  • 实现异步更新

    Vue 和 React 都实现了异步更新策略。虽然实现的方式不尽相同,但都达到了减少 DOM 操作、避免过度渲染的目的。当我们使用 Vue 或 React 提供的接口去更新数据时,这个更新并不会立即生效,而是会被推入到一个队列里。待到适当的时机,队列中的更新任务会被批量触发,这就是异步更新。异步更新的特性在于它只看结果,因此渲染引擎不需要为过程买单

  • 避免可能会引发回流与重绘的DOM操作

    • 将导火索缓存起来,避免频繁改动
    • 避免逐条改变样式,使用类名去合并样式
    • 将DOM“离线”
    • Chrome的Flush队列

11. 优化首屏体验(图片懒加载Lazy-Load)

我们可以监听滚动事件,懒加载实现是根据如果可视区域高度大于元素顶部距可视区域顶部的高度,说明元素露出,这时给元素写入真实的src,展示图片

<script>
    // 获取所有的图片标签
    const imgs = document.getElementsByTagName('img')
    // 获取可视区域的高度
    const viewHeight = window.innerHeight || document.documentElement.clientHeight
    // num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
    let num = 0
    function lazyload(){
        for(let i=num; i<imgs.length; i++) {
            // 用可视区域高度减去元素顶部距离可视区域顶部的高度
            let distance = viewHeight - imgs[i].getBoundingClientRect().top
            // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
            if(distance >= 0 ){
                // 给元素写入真实的src,展示图片
                imgs[i].src = imgs[i].getAttribute('data-src')
                // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
                num = i + 1
            }
        }
    }
    // 监听Scroll事件
    window.addEventListener('scroll', lazyload, false);
</script>

12. 事件的节流(throttle)与防抖(debounce)

**scroll 事件,resize 事件、鼠标事件(比如 mousemove、mouseover 等)、键盘事件(keyup、keydown 等)**都存在被频繁触发的风险。频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。为了规避这种情况,我们需要一些手段来控制事件被触发的频率。就是在这样的背景下,throttle(事件节流)和 debounce(事件防抖)出现了。它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率

13. HTML代码结构优化

13.1 正确布置行内脚本

  • 尽可能使用外部脚本和样式文本 使用分离的样式表和脚本可以使其被浏览器缓存,本站的其他页面能够重用该文件。

  • 将样式表放在顶部 把样式表放在文档底部会导致浏览器阻止页面逐步呈现,这会导致用户看不到页面上内容的,在浏览器等待文档底部的样式表的时候,会延迟显示任何可视化组件。在IE中会导致白屏现象。将样式表放在文档的顶部则能很好地解决白屏现象,使页面逐步呈现。

  • 脚本尽可能移到底部

    • 脚本放在顶部带来的问题:

      1) 使用脚本时,对于位于脚本以下的内容,逐步呈现将被阻塞 2) 在下载脚本时会阻塞并行下载 放在底部可能会出现JS错误问题,当脚本没加载进来,用户就触发脚本事件。所以要综合考虑情况。

  • Script延迟加载 defer属性(IE & FF3.1+)、setTimeout

  • 避免CSS表达式对于 IE ,其支持 CSS 表达式,如下:

    width:expression(document.body.clientWidth < 600 ? “600px” : “auto”);
    

    其他浏览器会忽略该属性,但是 IE 认识。这个写法的性能低下之处,在于其更新频率远远超出你的预估。它不单单会在页面大小改变的时候求值,用户滚动页面,鼠标移动这都会进行求值。所以需要避免使用CSS表达式,必要的时候使用JavaScript处理。

    13.2 少用iframe

优点:可以和主页面并行加载 缺点: iframe会阻塞onload事件 解决:onload事件后设置iframe的src,或者JS创建iframe节点 和主页面使用同一个连接池 避免src为空—为空默认为主页面地址

13.3 减小DOM结构的层级

DOM层级越深会增加 CSS rule Tree 和 Dom Tree 匹配构造的性能

13.4 尽量用div取代table,或者将table打破成嵌套层次深的结构

table会影响页面呈现的速度,只有table里的内容全部加载完才会显示

13.5 减小Cookie的大小

因为在同一域名下,只要有一个请求,cookie都会被传来传去,不管这次请求是否用到,所以需要减小cookie的大小

相关书籍与资料:

《高性能网站建设指南》

《高性能网站建设指南》笔记