性能优化

我觉得对于软件工程来说,身为一个合格的开发者,要善于探索和发现。互联网给了我们丰富的开源技术和海量的信息,为何不去饥渴的去探索呢。下面就一起去探索这份全面而强大的性能优化指南吧。

Loading Performance(加载性能)

怎么知道自己的网站有哪些性能问题呢?那当然要借助一些工具来检查:What are the different performance tools?

优化内容效率的主要措施:

  • 避免不必要的下载
  • 优化资源编码&大小(优化基于文本的资产的编码和传送大小)
  • 图像优化
  • 自动优化图像
  • 用视频替换动画GIF
  • 网页字体优化
  • HTTP 缓存

JavaScript 启动优化

JavaScript 启动优化也包括几点,如下:

  • HTTP 缓存
  • CSS 和 JS 代码覆盖率
  • 分析其 webpack 软件包
  • 防止内存泄漏
  • PRPL 模式
  • JavaScript 启动性能
  • Chrome 开发峰会 2017 - 现代加载最佳做法

优化JavaScript

  • 使用Tree Shaking减少JavaScript负载
  • 使用代码拆分减少JavaScript负载

关键渲染路径(CRP:Critical Rendering Path)

  • 构建对象模型
  • 渲染树构建、布局及绘制
  • 阻塞渲染的 CSS
  • 使用 JavaScript 添加交互
  • 评估关键渲染路径
  • 分析关键渲染路径性能
  • 优化关键渲染路径
  • PageSpeed 规则和建议

Rendering Performance(渲染性能)

要编写高性能的网站和应用,您需要了解浏览器如何处理 HTML、JavaScript 和 CSS,并确保您编写的代码(和您要包括的其他第三方代码)尽可能高效地运行。

  • JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations、Transitions 和 Web Animation API。
  • 样式计算。此过程是根据匹配选择器(例如 .headline 或 .nav > .nav__item)计算出哪些元素应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。
  • 布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。
  • 绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  • 合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

以上就是性能优化的主要方式。把这些链接点点,把文章看看,你会发现性能优化是如此的有趣。当然,自此做性能优化也就变得简单了起来,哈哈。

还有一些相关的工具,也罗列一下:

HTTP缓存

HTTP 有一系列的规范来规定哪些情况下需要缓存请求信息、缓存多久,而哪些情况下不能进行信息的缓存。我们可以通过相关的 HTTP 请求头来实现缓存。HTTP 缓存大致可以分为强缓存与协商缓存。

强缓存

在强缓存的情况下,浏览器不会向服务器发送请求,而是直接从本地缓存中读取内容,这个“本地”一般就是来源于硬盘。这也就是我们在 Chrome DevTools 上经常看到的「disk cache」。在 Expires 上可以设置一个过期时间,浏览器通过将其与当前本地时间对比,判断资源是否过期,未过期则直接从本地取即可。而 Cache-Control 则可以通过给它设置一个 max-age,来控制过期时间。例如,max-age=300 就是表示在响应成功后 300 秒内,资源请求会走强缓存。

协商缓存

协商缓存是为了解决这样的问题:如果服务器在300秒内更新了资源,需要怎么让客户端知道并更新。

一种协商缓存的方式是:服务器第一次响应时返回Last-Modified,而浏览器在后续请求时带上其值作为If-Modified-Since,相当于问服务端:XX 时间点之后,这个资源更新了么?服务器根据实际情况回答即可:更新了(状态码 200)或没更新(状态码 304)。

上面是通过时间来判断是否更新,如果更新时间间隔过短,例如 1秒 一下,那么使用更新时间的方式精度就不够了。所以还有一种是通过标识 —— ETag。服务器第一次响应时返回ETag,而浏览器在后续请求时带上其值作为If-None-Match。一般会用文件的 MD5 作为 ETag。

浏览器缓存

当所有缓存都没有命中时候则会进入浏览器缓存,浏览器缓存主要分为

Service Worker

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的

Memory Cache

内存中的缓存,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。

Disk Cache

存储在硬盘中的缓存,它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据

Push Cache

推送缓存当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

DNS预解析

基本我们访问远程服务的时候,不会直接使用服务的出口 IP,而是使用域名。所以请求的一个重要环节就是域名解析。这是一个比较耗时的过程,我们可以通过以下手段进行加速

DNS Prefetch

提前解析将要使用的DNS

1
<link rel="dns-prefetch" href="//m1.lefile.com">

reconnect

不仅要求浏览器预解析指定域名的DNS,还需要预先链接

1
<link rel="preconnect" href="//m1.lefile.cn/" crossorigin="anonymous" />

prefetch

要求浏览器获取整个的指定资源但是不允许浏览器对资源做预处理和执行(告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源)

1
<link rel="prefetch" href="//p1.lefile.cn/fes/cms/2018/11/23/4tz6y9vx1ktskvw86pxr56uw8bu7gx527894.png" crossorigin as="image" />

preload

与prefetch差不多,但它会告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源

1
<link rel="preload"" href="https://p1.lefile.cn/fes/cms/2018/11/23/4tz6y9vx1ktskvw86pxr56uw8bu7gx527894.png" crossorigin as="image" />

prerender

允许浏览器对资源做预处理和执行

1
<link rel="prerender" href="https://m2.lefile.cn/global/js/jquery-1.11.1.min.js" />

上面的方法需要在head里面添加:

1
<meta http-equiv="x-dns-prefetch-control" content="on">

使用CDN

CDN 其实是 Content Delivery Network 的缩写,即“内容分发网络”。

CDN主要功能是在不同的地点缓存内容,通过负载均衡技术,将用户的请求定向到最合适的缓存服务器上去获取内容。与传统访问方式不同,CDN网络则是在用户和服务器之间增加缓存层,将用户的访问请求引导到最优的缓存节点而不是服务器源站点,从而加速访问速度。

keep-alive

我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议);当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。keep-Alive优点:更高效,性能更高

减少网络发送的数据量

  • 采用精简(移除注释,空格)、混淆(额外缩短变量名)的方式
  • gzip压缩,现在绝大部分浏览器都支持gzip压缩
  • 部分资源可考虑采用内联
  • 静态资源采用浏览器缓存,时间要长
  • 删除无用的代码

减少关键资源的数量,移除非关键渲染资源

  • css默认会生成cssom。通过非关键资源拆出来,单独外联引入,增加media,则浏览器只会下载,不会解析(用到的时候解析)。但是此时额外增加了网络请求,需要权衡
  • js执行会等待cssom构建完毕,并且会阻塞dom的构建。动态引入js

减少阻塞解析的js

  • 异步加载 async
  • defer、脚本放到最底部

渲染层合并

合成层好处:

  • 提升动画效果
  • 减少绘制区域
  • 合理管理合成层
  • 合成层的方法:will-change 设置为 opacity、transform、top、left、bottom、right 可以将元素提升为合成层。

对于那些目前还不支持 will-change 属性的浏览器,目前常用的是使用一个 3D transform 属性来强制提升为合成层。

预加载

预加载技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <!-- 预先解析指定域名的DNS -->
<link rel="dns-prefetch" href="https://m1.lefile.cn/" />

<!-- 不仅要求浏览器预解析指定域名的DNS,还需要预先链接,-->

<link rel="preconnect" href="https://m1.lefile.cn/" crossorigin="anonymous" />

<!-- 预获取,在Preconnect基础上更进一步,它要求浏览器获取整个的指定资源但是不允许浏览器对资源做预处理和执行-->
<!-- prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源 -->
<link rel="prefetch" href="https://p1.lefile.cn/fes/cms/2018/11/23/4tz6y9vx1ktskvw86pxr56uw8bu7gx527894.png" crossorigin as="image" />

<!-- preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源 -->
<link rel="preload"" href="https://p1.lefile.cn/fes/cms/2018/11/23/4tz6y9vx1ktskvw86pxr56uw8bu7gx527894.png" crossorigin as="image" />

<!-- 允许浏览器对资源做预处理和执行 -->
<link rel="prerender" href="https://m2.lefile.cn/global/js/jquery-1.11.1.min.js" />

在webpack中使用预请求

预加载可以配合 code split 来使用,可以在降低初始加载量的情况下,尽量保证按需加载时的体验

1
2
3
4
5
// prefetch
import(/* webpackPrefetch: true */ './sub1.js');

// preload
import(/* webpackPreload: true */ './sub2.js')