目录

参考资料

1、常用网站性能优化指标

1.1 网页的资源请求与加载阶段

1.2 网页渲染阶段

2.Yahoo性能优化法则

3.性能优化工具

4.vue项目优化


参考资料

《移动Web前端高效开发实践》iKcamp著

https://juejin.im/post/5bfd8a4451882509a7681858#heading-12

https://juejin.im/entry/594778b0ac502e006ba8a471

https://juejin.im/post/5c011e0c5188252ea66afdfa#heading-1

1、常用网站性能优化指标

1.1 网页的资源请求与加载阶段

对于网站性能指标最直接的印象可能是网站的响应速度,因为这是访问者最直观真实的体验。网站访问的过程从用户输入网站域名开始,通过DNS解析找到目标服务器,目标服务器收到请求后执行服务器及数据库等一系列操作,并将响应数据经过互联网发送到用户浏览器中,最终浏览器处理响应数据并完成网页的渲染呈现。

打开Chrome浏览器的DevTools,可以看到用户访问网站发送资源请求的全过程。下面我们打开B站暗中观察下...

在建立TCP连接的阶段(HTTP协议是建立在TCP协议之上的),Queuing和Stalled表示请求队列以及请求等待的时间。DNS Lookup表示执行DNS查询所用的时间。页面上的每一个新域都需要完整的往返才能执行DNS查询。Initila connection和SSL包括TCP握手重试和协商SSL以及SSL握手的时间。

在请求响应的阶段,Request sent是发出网络请求所用的时间,通常不会超过1ms。Watiting(TTFB)是等待初始响应所用的时间,也称为等待返回首个字节的时间,该时间将捕捉到服务器往返的延迟时间,以及等待服务器传送响应所用的时间。Content Download则是从服务器上接收数据的时间。

由此可见,为了实现网站的快速响应,首先需要考虑的就是减少资源访问以及加载阶段所消耗的时间。这里提供一下几种方案:

1.划分子域

Chrome浏览器只允许每个源拥有6个TCP连接,因此可以通过划分子域的方式,将多个资源分布在不同子域上用来减少请求队列的等待时间。然而,划分子域并不是一劳永逸的方式,多个子域意味着更多的DNS查询时间。通常划分为3到5个比较合适。

下面截取掘金文章中的一段关于域名拆分的描述(应该就是划分子域的意思吧)

什么叫拆分域名?很多公司初始项目搭建,都只申请了一个域名,站点的所有内容(html/php/jsp、js、css、img等都放在一个域名下),域名拆分主要为了增加浏览器资源请求的并行度即并发问题,让浏览器能同时发起更多的请求,也解决了请求默认携带的cookie问题,减少了数据传输字节;

如何拆分?以现在前后端分离式开发为例,建议分为三大类:

前端类 - 项目业务本身的htm、css、js、图标/片等;

静态类 - 即上述提到的CDN资源类;

动态类 - 可归为后端API接口类

2.保持持久连接

HTTP是一个无状态的面向连接的协议,即每个HTTP请求都是独立的。然而无状态并不代表HTTP不能保持TCP连接,Keep-Alive正是HTTP协议中保持TCP连接非常重要的一个属性。HTTP1.1协议中,Keep-Alive默认打开,使得通信双方在完成一次通信后仍然保持一定时长的连接,因此浏览器可以在一个单独的连接上进行多个请求,有效地降低建立TCP请求所消耗的时间。HTTP2.0也提供了更多更好的特性,具体怎么做呢?(我也不造)

3.DNS预解析

DNS预解析通过设置meta标签实现

例如:

不知道在哪看到的,使用dns-prefetch对项目中用到的域名进行 DNS 预解析,减少 DNS 查询时间

------ 请求响应阶段 ------

4.CND加速

使用CND加速可以减少客户端到服务器的网络距离,下面继续给出截取的掘金文章中的一段:

  1. CDN的意图就是尽可能的减少资源在转发、传输、链路抖动等情况下顺利保障信息的连贯性;
  2. 通俗的讲就是CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上———曾经人们都说距离产生美,后来变了都说距离产生小三,在这里距离产生的是用户跑路了,所以足以说明CDN的重要性;
  3. CDN采用各节点缓存的机制缓存很严重,当我们项目的静态资源(只是之前存放在cdn上的资源)修改后,如果CDN缓存没有做相应更新,则看到的还是旧的网页,解决的办法是刷新缓存,七牛云、腾讯云都可单独针对某个文件/目录进行刷新;
  4. 广告常说:XX酒虽好,可不要贪杯哦,CDN托管也是如此,合理使用:图片、常用js组件、css重置样式等,即不常改动的文件即可走CDN,包括项目内的一些介绍页;

要做CND加速的话百度一下找几个提供这种服务的机构,什么阿里云七牛云啊看着教程弄......

5.设置缓存

推荐阅读http://louiszhai.github.io/2017/04/07/http-cache/#ETag

设置服务器缓存并通过文件头设置Expires或Cache-Control来控制缓存,达到减少TTFB时间的目的,在处理访问静态资源的性能优化上,该方法尤其重要。

现在浏览器会根据HTTP协议中的缓存,实现本地缓存功能。在请求一个新的文件时,浏览器发送HTTP请求到服务端。接到服务端的响应后,浏览器会将请求的资源存储在本地,留作以后使用。

服务端响应头中,会带有文件相关的缓存策略,告诉浏览器文件是否需要缓存以及缓存何时过期等信息。当浏览器再次请求文件时,会先判断缓存中是否有相应的文件以及是否过期,未过期则直接从缓存中读取文件,不会再向服务器发送请求。

 

HTTP头中与缓存相关的属性,主要有以下几个:

(1) Expires: 指定缓存过期的时间,是一个绝对时间,但受客户端和服务端时钟和时区差异的影响

(2) Cache-Control:比Expires策略更详细,优先级比Expires高,其值可以是以下五种情况

  • no-cache: 告诉客户端不要使用缓存
  • no-store: 告诉客户端不要响应缓存(禁止使用缓存,每一次都重新请求数据)
  • public: 缓存响应,并可以在多用户间共享
  • private: 缓存响应,但不能在多用户间共享
  • max-age: 缓存在指定时间(单位为秒)后过期

(3) Last-Modified/If-Modified-Since: 指定响应的最后修改时间。如果响应头中包含Last-Modified,再次请求时通过If-Modified-Since将最后修改时间告诉服务端,服务端判断文件是否有过修改,再决定返回新内容还是通过HTTP状态码304告诉客户端使用缓存。

(4) Etag/If-None-Match: 区别资源内容的唯一标识,需要配合Cache-Control使用。当文件最后修改时间发生变化,但文件内容并无改变时,也应该使用缓存。如果响应头中包含Etag,再次请求时通过If-None-Match将内容标识告诉服务端,服务端比较内容是否有改变后,再决定返回新内容还是通过HTTP状态码304告诉客户端使用缓存。

1.2 网页渲染阶段

继续暗中观察B站

Chrome中上图表示网页各阶段时间消耗

Rendering和Painting就是浏览器在页面渲染过程中的时间消耗。先了解下浏览器是如何渲染网页的。(整理自参考资料的书籍,网上有各种解释......)

(1) 解析HTML文件构建DOM树浏览器将从服务器获取的HTML文档构建成文档对象模型DOM(Document Object Model), 与此同时浏览器会下载文档中引用的CSS与JavaScript文件。

(2) 解析CSS文件构建CSSOM:经过解析构成层叠样式表模型CSSOM(CSS Object Model),而JavaScript则会交给JavaScript引擎执行。

(3) 利用DOM和CSSOM构建Render Tree:紧接着,DOM与CSSOM将构建渲染树(Render Tree),Render Tree上的节点称为RenderObject。DOM Tree上的每个节点对象会递归检查是否需要创建RenderObject,并根据DOM节点类型创建RenderObject节点,动态加入DOM元素。

(4) 根据Render Tree生成Render Layout Tree:由于页面上的非可见元素不会形成RenderObject,因此Render Tree上的节点并不与DOM Tree一一对应。为了方便处理定位、Z轴排序、页内滚动等问题,浏览器并不以Render Tree为基础直接进行渲染,而是依据RenderObject生成新的Render Layout Tree,

(5) 根据Render Layer Tree绘制: 浏览器渲染引擎将会遍历Render Layer Tree,访问每一个RenderLayer,再遍历从属于这个RenderLayer的RenderObject,将每一个RenderObject绘制出来,执行Composite合并RenderLayer并最终呈现给用户。

渲染过程中遇到JS文件会怎么样?

渲染过程中,如果遇到<script>就停止渲染,执行 JS 代码。因为浏览器有GUI渲染线程与JS引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。 JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。

也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。

渲染阶段的优化

1.小图标的使用

小图标用iconfont代替,将图片通过iconmoon转换后使用css导入到项目中,添加对应的class即可使用对应的图标。

2.使用base64格式的图片

有些小图片,可能色彩比较复杂,这个时候再用iconfont就有点不合适了,此时可以将其转化为base64格式(不能缓存),直接嵌在src中,比如webpack的url-loader设置limit参数即可

3.使用WebP格式的图片

WebP最初在2010年发布,目标是减少文件大小,支持无损、有损压缩,动态、静态图片,压缩比率优于 GIF、JPEG、JPEG2000、PNG 等格式,非常适合用于网络等图片传输,现在开始已经被越来越多的浏览器支持,当然 WebP 格式也有它的缺点,算法相对其他格式更加复杂,会在节省流量资源的同时会占用计算资源,对计算机造成更大的负担,WebP支持的像素最大数量是16383x16383。有损压缩的WebP仅支持8-bit的YUV4:2:0格式。而无损压缩(可逆压缩)的WebP支持VP8L编码与8-bit之ARGB色彩空间。又无论是有损或无损压缩皆支持Alpha透明通道、ICC色彩配置、XMP诠释数据,更详细支持说明https://caniuse.com/#search=webp

优势:

体积小几乎可以毫不夸张的说,已经小的不能再小了;

小而美的同时,还质量好,几乎看不出来与原图差别;

曾经的动态图gif、jpeg压缩都会不清晰,但现在对它来说都是so easy~。

缺点/困难:

目前并不是所有浏览器都支持WebP,因此需要解决浏览器适配问题;

对于已上线的项目,采用WebP需要替换大量图片,工作量太大(不确定后台程序是否能搞定)。

4.图片懒加载和预加载

懒加载:场景---电商图片

  1. 图片进入可视区域之后请求图片资源
  2. 对于电商等图片很多,页面很长的业务场景适用
  3. 减少无效资源的加载
  4. 并发加载的资源过多会会阻塞js的加载,影响网站的正常使用

原生js实现:

var viewheight = document.documentElement.clientHeight   //可视区域高度

function lazyload(){
    var eles = document.querySelectorAll('img[data-original][lazyload]')

    Array.prototype.forEach.call(eles,function(item,index){
        var rect;
        if(item.dataset.original === '') return;
        rect = item.getBoundingClientRect(); //返回元素的大小及其相对于视口的

        if(rect.bottom >= 0 && rect.top < viewheight){
            !function(){
                var img = new Image();
                img.src = item.dataset.url;
                img.onload = function(){
                    item.src = img.src
                }
                item.removeAttribute('data-original');
                item.removeAttribute('lazyload');
            }()
        }
    })
}

lazyload()
document.addEventListener('scroll',lazyload)

预加载:场景---抽奖

  1. 图片等静态资源在使用之前的提前请求
  2. 资源使用到时能从缓存中加载,提升用户体验
  3. 页面展示的依赖关系维护

通过vue-lazyload插件实现图片懒加载

Vue.use(VueLazyload, {
  loading: require('./assets/imgs/loading.gif'),
  listenEvents: ['scroll'],
  filter: {
    webp(listener, options) {
      if (!options.supportWebp) return
      const isCDN = /xiaohuochai.site/
      if (isCDN.test(listener.src)) {
        listener.src += '?imageView2/2/format/webp'
      }
    }
  }
})

5.链接位置

理解浏览器渲染页面的过程,<link>标签放在<head>中、js放在</body>结束前,并根据需要使用js的异步加载(async、defer)

6.尽量减少回流与重绘

执行javaScript的解析和UI渲染的两个浏览器线程是互斥的,UI渲染时JS代码解析终止,反之亦然。

页面布局几何属性改变时,就会触发回流。

当需要更新的只是元素的某些外观时,就会触发重绘。

  1. 用translate替代top属性:top会触发layout,但translate不会
  2. 用opacity代替visibility:opacity不会触发重绘也不会触发回流,只是改变图层alpha值,但是必须要将这个图片独立出一个图层
  3. visibility会触发重绘
  4. 不要一条一条的修改DOM的样式,预先定义好class,然后修改DOM的className
  5. 把DOM离线后修改,比如:先把DOM给display:none(有一次reflow),然后你修改100次,然后再把它显示出来
  6. 不要把DOM节点的属性值放在一个循环里当成循环的变量
  7. offsetHeight、offsetWidth每次都要刷新缓冲区,缓冲机制被破坏,先用变量存储下来
  8. 不要使用table布局,可能很小的一个小改动会造成整个table的重新布局
  9. div只会影响后续样式的布局
  10. 动画实现的速度的选择:选择合适的动画速度
  11. 启用gpu硬件加速(并行运算),gpu加速意味着数据需要从cpu走总线到gpu传输,需要考虑传输损耗.

transform:translateZ(0)

transform:translate3D(0)

7.编写高效率的CSS

使用CSS预处理器时注意不要有过多的嵌套,嵌套层次过深会影响浏览器查找选择器的速度,且一定程度上会产生出很多冗余的字节。

8.减少DOM元素数量、减少DOM的操作

减少 DOM 元素数量,合理利用:after、:before等伪类,避免页面过深的层级嵌套;

优化javascript性能,减少DOM操作次数(或集中操作),能有效规避页面重绘/重排;

只能说尽可能去做优化,如数据分页、首屏直出、按需加载等

9.函数节流

为触发频率较高的函数使用函数节流

/**
 * 函数节流
 * @param {fn} function test(){}
 * @return {fn} function test(){}
 */
export const throttle = (fn, wait = 100) => function func(...args) {
  if (fn.timer) return
  fn.timer = setTimeout(() => {
    fn.apply(this, args)
    fn.timer = null
  }, wait)
}

2.Yahoo性能优化法则

互联网时代的先行者雅虎针对如何提高Web性能提出了许多优化建议,这些建议被称为“雅虎法则”/“雅虎军规”,下面列出最常用的几条:

1.减少HTTP请求

减少HTTP请求的手段有很多,如:

  • 合并CSS与JavaScript文件
  • 图片、图标sprites合并,或使用iconfont图标,或者SVG Sprites
  • 内联图像“data:URL scheme”
  • 资源按需加载,用到什么加载什么
  • 前端数据的缓存

合理使用dns-prefetch、prefetch、preload、defer、async

dns-prefetch:使用dns-prefetch对项目中用到的域名进行 DNS 预解析,减少 DNS 查询

prefetch: 它是一个优先级非常低的资源加载标识,浏览器会在空闲时(即主进程资源加载完成后)下载带有 prefetch标识的资源并缓存到disk,在后续模块使用到这个文件的时候,会直接从缓存读取;该功能webpack有个插件,配置后编译能自动插入到页面上;

preload:它是一个可以预加载资源的属性,详细说明请看官方API,一般情况下我们可能会对接下来的业务需要的audio、img、font、script等资源进行预先加载(甚至是下一个路由页面),这样能达到0秒打开页面的效果

比如:

2. 压缩CSS和JS代码

精简不必要的空格、换行、缩进等。代码的字节数减少、代码对应的下载时间也会随之减少。这个Webpack等打包工具都有相关的插件。

3.去除重复引用的脚本

4.可缓存的AJAX

通过Expire或者Cache-Control头来实现缓存,可以从缓存中读取内容,提升性能

5.延迟加载非必要脚本

页面上有些内容并不需要立即加载。非首屏的图片资源,需要经过用户操作才能呈现的非可见元素等都可以进行推迟加载。

6.预加载

预加载指在浏览器空闲阶段预先加载将来用户可能会访问到的内容,从而提高页面的即时响应能力,优化用户体验。

7.减少DOM元素数量

DOM元素过多意味着需要加载更多的数据,在使用JavaScript遍历DOM时更加低效,也意味着执行布局和绘制时产生过多的性能损耗。

8.减少DOM访问次数

DOM被认为天生即慢,通常需要将经常访问的DOM对象缓存,避免过多地使用JavaScript修改页面布局

9.避免使用iframe

即使引入页面内容为空,iframe也需要消耗时间下载,会阻止页面加载,并且缺少语义(H5 API貌似做了些改进?)

10.优化图片

3.性能优化工具

待整理......

4.vue项目优化

v-show v-if

v-show,v-if 用哪个?在我来看要分两个维度去思考问题,第一个维度是权限问题,只要涉及到权限相关的展示无疑要用 v-if,第二个维度在没有权限限制下根据用户点击的频次选择,频繁切换的使用 v-show,不频繁切换的使用 v-if,这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if,因为减少了 dom 数量,加快首屏渲染,至于性能方面我感觉肉眼看不出来切换的渲染过程,也不会影响用户的体验。

组件懒加载

component: () => import('./views/store/StoreDetail.vue')

打包

打包 vender 时不打包 vue、vuex、vue-router、axios 等,换用国内的 bootcdn 直接引入到根目录的 index.html 中。

例如:

<script src="//cdn.bootcss.com/vue/2.2.5/vue.min.js"></script>

<script src="//cdn.bootcss.com/vue-router/2.3.0/vue-router.min.js"></script>

<script src="//cdn.bootcss.com/vuex/2.2.1/vuex.min.js"></script>

<script src="//cdn.bootcss.com/axios/0.15.3/axios.min.js"></script>

在 webpack 里有个 externals,可以忽略不需要打包的库

externals: {

  'vue': 'Vue',

  'vue-router': 'VueRouter',

  'vuex': 'Vuex',

  'axios': 'axios'

}

此时的 vender 包会非常小,如果不够小还可以拆分其他的库,此时增加了请求的数量,但是远比加载一个较大的 bundle 快的多

(未检验)