前端项目优化 -Web 开发经常使用优化方案、Vue & React 项目优化

github

github-myBlob
css

从输入URL到页面加载完成的整个过程


  1. 首先作 DNS 查询,若是这一步作了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来
  2. 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,而后下发给网络层。网络层中的 IP 协议会肯定 IP 地址,而且指示了数据传输中如何跳转路由器。而后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了
  3. TCP 握手结束后会进行 TLS 握手,而后就开始正式的传输数据(若是使用HTTPS)
  4. 数据在进入服务端以前,可能还会先通过负责负载均衡的服务器,它的做用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件
  5. 首先浏览器会判断状态码是什么,若是是 200 那就继续解析,若是 400 或 500 的话就会报错,若是 300 的话会进行重定向,这里会有个重定向计数器,避免过屡次的重定向,超过次数也会报错
  6. 浏览器开始解析文件,若是是 gzip 格式的话会先解压一下,而后经过文件的编码格式知道该如何去解码文件
  7. 文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。若是遇到script标签的话,会判断是否存在async或者defer,前者会并行进行下载并执行 JS,后者会先下载文件,而后等待 HTML 解析完成后顺序执行,若是以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里若是使用 HTTP 2.0 协议的话会极大的提升多图的下载效率。
  8. 初始的 HTML 被彻底加载和解析后会触发DOMContentLoaded事件
  9. CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是肯定页面元素的布局、样式等等诸多方面的东西
  10. 在生成 Render 树的过程当中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了
  11. 没有要传输的文件了,断开TCP链接 4 次挥手

性能优化分析


根据上面的过程能够看到,页面的加载过程主要分为下载、解析、渲染三个步骤,总体能够从两个角度来考虑:html

  • 网页的资源请求与加载阶段
  • 网页渲染阶段

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

咱们能够打开 Chrome 的调试工具来分析此阶段的性能指标
在这里插入图片描述
在创建 TCP 链接的阶段(HTTP 协议是创建在 TCP 协议之上的)前端

  • Queuing 和 Stalled 表示请求队列以及请求等待的时间
  • DNS Lookup 表示执行 DNS 查询所用的时间。页面上的每个新域都须要完整的往返才能执行DNS查询
  • Initila connection 和 SSL 包括 TCP 握手重试和协商 SSL 以及 SSL 握手的时间。

在请求响应的阶段vue

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

资源请求阶段优化方案

依据上面的指标给出如下几点优化方案(仅供参考)react

一、划分子域

条件:拥有多个域名
Chrome 浏览器只容许每一个源拥有 6 个 TCP 链接,所以能够经过划分子域的方式,将多个资源分布在不一样子域上用来减小请求队列的等待时间。然而,划分子域并非一劳永逸的方式,多个子域意味着更多的 DNS 查询时间。一般划分为 3 到 5 个比较合适。
对如何拆分资源有以下建议:webpack

  • 前端类:把项目业务自己的 html、css、js、图标等归为一类
  • 静态类:CDN 资源
  • 动态类:后端 API

二、DNS 预解析

DNS 解析也是须要时间的,能够经过预解析的方式来预先得到域名所对应的 IP,方法是在 head 标签里写上几个 link 标签ios

<link rel="dns-prefetch" href="https://www.google.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com">

对以上几个网站提早解析,这个过程是并行的,不会阻塞页面渲染。git

三、预加载

在开发中,可能会遇到这样的状况。有些资源不须要立刻用到,可是但愿尽早获取,这时候就可使用预加载。
预加载实际上是声明式的 fetch,强制浏览器请求资源,而且不会阻塞 onload 事件,可使用如下代码开启预加载:github

<link rel="preload" href="http://example.com">

预加载能够必定程度上下降首屏的加载时间,由于能够将一些不影响首屏但重要的文件延后加载,惟一缺点就是兼容性很差。web

四、保持持久链接

HTTP 是一个无状态的面向链接的协议,即每一个 HTTP 请求都是独立的。然而无状态并不表明 HTTP 不能保持 TCP 链接,Keep-Alive 正是 HTTP 协议中保持 TCP 链接很是重要的一个属性。 HTTP1.1 协议中,Keep-Alive 默认打开,使得通讯双方在完成一次通讯后仍然保持必定时长的链接,所以浏览器能够在一个单独的链接上进行多个请求,有效地下降创建 TCP 请求所消耗的时间。

五、CND 加速

使用 CND 加速能够减小客户端到服务器的网络距离。

  • CDN 的意图就是尽量地减小资源在转发、传输、链路抖动等状况下顺利保障信息的连贯性;
  • CDN 系统可以实时地根据网络流量和各节点的链接、负载情况以及到用户的距离和响应时间等综合信息将用户的请求从新导向离用户最近的服务节点上
  • CDN 采用各节点缓存的机制,当咱们项目的静态资源修改后,若是 CDN 缓存没有作相应更新,则看到的仍是旧的网页,解决的办法是刷新缓存,七牛云、腾讯云均可单独针对某个文件/目录进行刷新;
  • CDN 缓存须要合理地使用:图片、经常使用 js 组件、css 重置样式等,即不常改动的文件可走 CDN,包括项目内的一些介绍页;

还有一种比较流行的作法是让一些项目依赖走 CDN,好比 vuex、vue-router 这些插件经过外链的形式来引入,由于它们都有本身免费的 CDN,这样能够减小打包后的文件体积。

六、设置缓存

缓存对于前端性能优化来讲是个很重要的点,良好的缓存策略能够下降资源的重复加载提升网页的总体加载速度。
一般浏览器缓存策略分为两种:强缓存协商缓存

  • 强缓存:实现强缓存能够经过两种响应头实现:ExpiresCache-Control强缓存表示在缓存期间不须要向服务器发送请求
  • 协商缓存:缓存过时了就是用协商缓存,其经过Last-Modified/If-Modified-SinceETag/If-None-Match实现

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

(1) Expires: 指定缓存过时的时间,是一个绝对时间,但受客户端和服务端时钟和时区差别的影响,是 HTTP/1.0 的产物
形如Expires: Wed, 22 Oct 2018 08:41:00 GMT

(2) Cache-Control:比 Expires 策略更详细,max-age 优先级比 Expires 高,其值能够是如下五种状况

  1. no-cache: 强制全部缓存了该响应的缓存用户,在使用已存储的缓存数据前,发送请求到原始服务器(进行过时认证),一般状况下,过时认证须要配合 etag 和 Last-Modified 进行一个比较
  2. no-store: 告诉客户端不要响应缓存(禁止使用缓存,每一次都从新请求数据)
  3. public: 缓存响应,并能够在多用户间共享(与中间代理服务器相关)
  4. private: 缓存响应,但不能在多用户间共享(与中间代理服务器相关)
  5. max-age: 缓存在指定时间(单位为秒)后过时

(3) Last-Modified / If-Modified-Since: Last-Modified表示本地文件最后修改日期,If-Modified-Since会将上次从服务器获取的Last-Modified的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。
可是若是(服务器)在本地打开缓存文件(或者删了个字符 a 后又填上去),就会形成Last-Modified被修改,因此在 HTTP / 1.1 出现了ETag

(4) Etag / If-None-Match: ETag相似于文件指纹,If-None-Match会将当前ETag发送给服务器,询问该资源ETag是否变更,有变更的话就将新的资源发送回来。而且ETag优先级比Last-Modified高。
因为 etag 要使用少数的字符表示一个不定大小的文件(如 etag: "58c4e2a1-f7"),因此 etag 是有重合的风险的,若是网站的信息特别重要,连很小的几率如百万分之一都不容许,那么就不要使用 etag 了。使用 etag 的代价是增长了服务器的计算负担,特别是当文件比较大时。

在这里插入图片描述

选择合适的缓存策略
对于大部分的场景均可以使用强缓存配合协商缓存解决,可是在一些特殊的地方可能须要选择特殊的缓存策略

  • 对于某些不须要缓存的资源,可使用Cache-control: no-store,表示该资源不须要缓存
  • 对于频繁变更的资源,可使用Cache-Control: no-cache并配合ETag使用,表示该资源已被缓存,可是每次都会发送请求询问资源是否更新。
  • 对于代码文件来讲,一般使用Cache-Control: max-age=31536000并配合策略缓存使用,而后对文件进行指纹处理,一旦文件名变更就会马上下载新的文件。

七、使用 HTTP / 2.0

由于浏览器会有并发请求限制,在 HTTP / 1.1 时代,每一个请求都须要创建和断开,消耗了好几个 RTT 时间,而且因为 TCP 慢启动的缘由,加载体积大的文件会须要更多的时间。
在 HTTP / 2.0 中引入了多路复用,可以让多个请求使用同一个 TCP 连接,极大的加快了网页的加载速度。而且还支持 Header 压缩,进一步的减小了请求的数据大小。

八、图片和文件压缩

这又涉及到不少知识点了,简单来讲,咱们要尽量地在保证咱们的 App 能正常运行、图片尽量保证高质量的前提下去压缩全部用到的文件的体积。好比图片格式的选择、去掉咱们代码中的注释、空行、无关代码等。
图片相关优化

  • 不用图片。不少时候会使用到不少修饰类图片,其实这类修饰图片彻底能够用 CSS 去代替。
  • 对于移动端来讲,屏幕宽度就那么点,彻底没有必要去加载原图浪费带宽。通常图片都用 CDN 加载,能够计算出适配屏幕的宽度,而后去请求相应裁剪好的图片。
  • 小图使用 base64 格式
  • 选择正确的图片格式:
    • 对于可以显示 WebP 格式的浏览器尽可能使用 WebP 格式。由于 WebP 格式具备更好的图像数据压缩算法,能带来更小的图片体积,并且拥有肉眼识别无差别的图像质量,缺点就是兼容性并很差
    • 小图使用 PNG,其实对于大部分图标这类图片,彻底可使用 SVG 代替
    • 照片使用 JPEG

构建工具的使用

  • 对于 Webpack4,打包项目使用 production 模式,这样会自动开启代码压缩
  • 使用 ES6 模块来开启 tree shaking,这个技术能够移除没有使用的代码
  • 优化图片,对于小图可使用 base64 的方式写入文件中
  • 按照路由拆分代码,实现按需加载
  • 给打包出来的文件名添加哈希,实现浏览器缓存文件(能及时更新)
  • 启用 gzip 压缩(须要先后端支持)
  • 各类 loader/plugin 的使用

压缩 HTML 文件
能够把 HTML 的注释去掉,把行前缩进删掉,这样处理的文件能够明显减小 HTML 的体积;这样作几乎是没有风险的,除了 pre 标签不可以去掉行首缩进以外,其余的都正常。

网页渲染阶段优化方案

一、<script>标签位置

渲染线程和 JS 引擎线程是互斥的,若是你想首屏渲染的越快,就越不该该在首屏就加载 JS 文件,所以建议将 script 标签放在 body 标签底部的缘由。或者使用给 script 标签添加 defer 或者 async 属性 。

  • defer 表示该文件会并行下载,可是会放到 HTML 解析完成后再顺序执行;
  • 对于没有任何依赖的 JS 文件能够加上 async 属性,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行

二、Webworker 的使用

执行 JS 代码过长会卡住渲染,对于须要不少时间计算的代码能够考虑使用 Webworker。Webworker 可让咱们另开一个线程执行脚本(这并无改变 JS 单线程的本质,由于新开的线程受控于主线程且不得操做 DOM)而不影响渲染。

三、懒加载

懒加载就是将不关键的资源延后加载。
懒加载的原理就是只加载自定义区域(一般是可视区域,但也能够是即将进入可视区域)内须要加载的东西。对于图片来讲,先设置图片标签的src属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为src属性,这样图片就会去下载资源,实现了图片懒加载。
懒加载不只能够用于图片,也可使用在别的资源上。好比进入可视区域才开始播放视频等等。

四、预加载

  • 图片等静态资源在使用以前的提早请求
  • 资源使用到时能从缓存中加载,提高用户体验
  • 页面展现的依赖关系维护

使用场景好比抽奖动画展现过程当中预先加载其余内容,或者电子书阅读章节的预加载可使切换下一章节时更为流畅。

五、减小回流与重绘

执行 JavaScript 的解析和 UI 渲染的两个浏览器线程是互斥的,UI 渲染时 JS 代码解析终止,反之亦然。
页面布局和几何属性 改变时,就会触发 回流
须要更新的只是元素的某些外观 时,就会触发 重绘

  • 用 translate 替代 top 属性:top 会触发 reflow,但 translate 不会
  • 不要一条一条地修改 DOM 的样式,预先定义好 class,而后修改 DOM 的 className
  • 把 DOM 离线后修改,好比:先把 DOM 给 display:none(有一次 reflow),而后你修改 100 次,而后再把它显示出来
  • 不要把 DOM 节点的属性值放在一个循环里当成循环的变量
  • offsetHeight、offsetWidth 每次都要刷新缓冲区,缓冲机制被破坏,先用变量存储下来
  • 不要使用 table 布局,可能很小的一个小改动会形成整个 table 的从新布局
  • 动画实现的速度的选择:选择合适的动画速度
  • 启用 gpu 硬件加速(并行运算),gpu 加速意味着数据须要从 cpu 走总线到 gpu 传输,须要考虑传输损耗.
    • transform:translateZ(0)
    • transform:translate3D(0)
    • 彷佛如今浏览器能智能地分析 gpu 加速了?

六、编写高效率的 CSS

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

七、减小 DOM 元素数量、减小 DOM 的操做

减小 DOM 元素数量,合理利用 :after、:before 等伪类,避免页面过深的层级嵌套;
优化 JavaScript 性能,减小 DOM 操做次数(或集中操做),能有效规避页面重绘/重排;
只能说尽量去作优化,如数据分页、首屏直出、按需加载等

八、函数节流

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

其余

SPA SEO SSR

SPA:单页面富应用
动态地重写页面的部分与用户交互而不是加载新的页面。
优势:① 先后端分离 ② 页面之间切换快 ③ 后端只需提供 API
缺点:① 首屏速度慢,由于用户首次加载 SPA 框架及应用程序的代码而后才渲染页面 ② 不利于 SEO
SEO(Search Engine Optimization):搜索引擎优化
经常使用技术:利用 <title> 标签和 <meta> 标签的 description

<html>
<head>
<title>标题内容</title>
  <meta name="description" content="描述内容">
  <meta name="keyword" content="关键字1,关键字2,—">
  </head>
</html>

SPA 应用中,一般经过 AJAX 获取数据,而这里就难以保证咱们的页面能被搜索引擎正常收录到。而且有一些搜索引擎不支持执行 JS 和经过 AJAX 获取数据,那就更不用提 SEO 了。
对于有些网站而言,SEO 显得相当重要,例如主要之内容输出为主的 Quora、stackoverflow、知乎和豆瓣等等,那如何才能正常使用 SPA 而又不影响 SEO 呢 ?因此有了 SSR
SSR(Server-Side Rendering):服务端渲染

如下内容部分参考《深刻浅出 React 与 Redux》- 程墨

为了量化网页性能,咱们定义两个指标:

  • TTFP(Time To First Paint):指的是从网页 HTTP 请求发出,到用户能够看到第一个有意义的内容渲染出来的时间差
  • TTI(Time To Interactive):指的是从网页 HTTP 请求发出,到用户能够对网页内容进行交互的时间

在一个 彻底靠浏览器端渲染 的应用中,当用户在浏览器中打开一个页面的时候,最坏状况下没有任何缓存,须要等待三个 HTTP 请求才能到达 TTFP 的时间点:

  • 向服务器获取 HTML,这个 HTML 只是一个无内容的空架子,可是皮之不存毛将焉附,这个 HTML 就是皮,在其中运行的 JavaScript 就是毛,因此这个请求时不可省略的
  • 获取 JavaScript 文件,大部分状况下,若是这是浏览器第二次访问这个网站,就能够直接读取缓存,不会发出真正的 HTTP 请求
  • 访问 API 服务器获取数据,获得的数据将由 JavaScript 加工以后用来填充 DOM 树,若是应用的是 React,那就是经过修改组件的状态或者属性来驱动渲染

而对于服务器端渲染,由于获取 HTTP 请求以后就会返回全部有内容的 HTML,因此在一个 HTTP 的周期以后就会提供给浏览器有意义的内容,因此首次渲染时间 TTFP 会优于彻底依赖于浏览器端渲染的页面。

除了更短的 TTFP,服务器端渲染还有一个好处就是利于搜索引擎优化,虽然某些搜索引擎已经可以索引浏览器端渲染的网页,可是毕竟不是全部搜索引擎都能作到这一点,让搜索引擎可以索引到应用页面的最直接方法就是提供完整 HTML

上面的性能对比只是理论上的分析,实际上,采用服务器端渲染是否能得到更好的 TTFP 有多方面因素。
一、服务器端产生的 HTML 过大是否会影响性能?
由于服务器端渲染返回的是完整的 HTML,那么下载这个 HTML 的时间也会增加。
二、服务器端渲染的运算消耗是不是服务器可以承担得起的?
浏览器端渲染的方案下,服务器只提供静态资源,压力被分摊到了访问用户的浏览器中;若是使用服务器端渲染,每一个页面请求都要产生 HTML 页面,这样服务器的压力就会很大。

React 并非给服务器端渲染设计的,若是应用对 TTFP 要求不高,也不但愿对 React 页面进行搜索引擎优化,那么没有必要使用“同构”来增长开发难度;若是但愿应用的性能能更进一步,并且服务器运算资源充足,那么能够尝试。对 Vue 而言应该也是一样的道理。


最后咱们来总结下服务端渲染理论上的优缺点:
优势:

  • 更快的响应时间、首屏加载时间,能够将 SEO 的关键信息直接在后台渲染成 HTML,从而保证搜索引擎的爬虫都能爬到关键数据
  • 更快的内容到达时间,特别是对于缓慢的网络状况或运行缓慢的设备
  • 无需等待全部的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,因此用户将会更快速地看到完整渲染的页面,一般能够产生更好的用户体验
  • 资源文件从本地请求(各类 bundle 什么的),更快的下载速度

缺点:

  • 占用服务器更多的 CPU 和内存资源
  • 一些经常使用的浏览器 API 可能没法使用,如 window、document、alert 等,若是须要使用的话须要对运行的环境加以判断
  • 开发难度加大

Vue 项目优化点

一、第三方库走 cdn
例如:

<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 选项,能够忽略不须要打包的库
https://webpack.js.org/configuration/externals/#root

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  externals: {
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'Vuex',
    'axios': 'axios'
  },
  output: {
    ...
  }
}

二、路由懒加载

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/ebook',
      component: () => import('./views/ebook/index.vue'), // 路由懒加载,这里用的是ES6的语法  import()函数是动态加载 import 是静态加载
      children: [ // 动态路由, 能够传递路径参数
        {
          path: ':fileName',
          component: () => import('./components/ebook/EbookReader.vue')
        }
      ]
    },
    {
      path: '/store',
      component: () => import('./views/store/index.vue'),
      redirect: '/store/shelf', // #/store -> #/store/home
      ...
    }
  ]
})

三、使用懒加载插件 Vue-Loader
具体的使用能够参考 这篇文章 或者去看官方文档
step1:cnpm install vue-lazyload --save
step2:main.js导入

import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyload)

step3:<img class="item-pic" v-lazy="newItem.picUrl"/>vue 文件中将须要懒加载的图片绑定v-bind:src修改成v-lazy
这只是图片懒加载,还有不少其余可选配置

四、v-ifv-show的选择
通常来讲,v-if有更高的切换开销,而v-show有更高的初始渲染开销。所以,若是须要很是频繁地切换,则使用v-show较好;若是在运行时条件不多改变,则使用v-if较好。

React 项目优化点

一、单个组件的优化:更改 shouldComponentUpdate 函数的默认实现,根据每一个 React 组件的内在逻辑定制其行为,减小没必要要的从新渲染

shouldComponentUpdate(nextProps, nextState) {
  // 假设影响渲染内容的 prop 只有 completed 和 text,只须要确保
  // 这两个 prop 没有变化,函数就能够返回 false
  return (nextProps.completed !== this.props.completed) ||
    (nextProps.text !== this.props.text)
}

二、使用 immutable.js 解决复杂数据 diff、clone 等问题。
immutable.js 实现原理:持久化数据结构,也就是使用旧数据建立新数据时,要保证旧数据同时可用且不变。同时为了不 deepCopy 把全部节点都复制一遍带来的性能损耗,Immutable 使用告终构共享,即若是对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

三、在 constructor() 里作 this 绑定
当在 render() 里使用事件处理方法时,提早在构造函数里把 this 绑定上去(若是须要的话),由于在每次 render 过程当中, 再调用 bind 都会新建一个新的函数,浪费资源.

// bad
class App extends React.Component {
  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv.bind(this)} />;
  }
}

// good
class App extends React.Component {
  constructor(props) {
    super(props);

    this.onClickDiv = this.onClickDiv.bind(this);
  }

  onClickDiv() {
    // do stuff
  }

  render() {
    return <div onClick={this.onClickDiv} />;
  }
}

四、基于路由的代码分割
使用React.lazyReact Router来配置基于路由的代码分割

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

参考资料

http://www.javashuo.com/article/p-ueqxqjhb-dz.html
http://www.javashuo.com/article/p-hiiezftc-do.html
http://www.javashuo.com/article/p-maytbjvw-kv.html
https://www.jianshu.com/p/333f390f2e84
https://yuchengkai.cn/docs/frontend/

相关文章
相关标签/搜索