前言
浏览器缓存设计一直是web性能优化中非常重要的一个环节,也是SPA应用盛行的今天不得不考虑的问题.作为一名优秀的前端工程师,为了让我们的应用更流畅,用户体验更好,我们有必要做好浏览器缓存策略.
每个Web应用体验都必须快速,对于渐进式 Web 应用更是如此。快速是指在屏幕上获取有意义内容所需的时间,要在不到 5 秒的时间内提供交互式体验。并且,它必须真的很快。很难形容可靠的高性能有多重要。可以这样想: 本机应用的首次加载令人沮丧。已安装的渐进式 Web 应用必须能让用户获得可靠的性能。
本文会介绍一些笔者曾经做过的Web性能优化方案以及浏览器缓存的基本流程,并会着重介绍如何利用浏览器缓存API封装适合自己团队的前端缓存库来极大地提高应用性能,并为公司省钱.
你将收获
熟悉浏览器缓存的基本过程
Web性能优化基本方案以及缓存策略为公司带来的价值
基于localStorage的缓存方案设计以及库的封装(vuex/redux数据持久化解决方案)
基于indexedDB的缓存方案设计以及库的封装
结合http请求库(axios/umi-request)进行更细粒度的缓存代理层设计
正文
1.浏览器缓存的基本过程
首先要想设计一个优秀的缓存策略,一定要了解浏览器缓存的流程,接下来是笔者总结的一个基本的流程图:
上图展示了一个基本的从浏览器请求到展示资源的过程,我们的缓存策略一部分可以从以上流程出发来做优化.我们都知道页面的缓存状态是由header决定的,下面具体介绍几个概念:
1. ETag
由服务端根据资源内容生成一段 hash 字符串,标识资源的状态,用户第一次请求时服务器会将ETag随着资源一起返回给浏览器, 再次请求时浏览器会将这串字符串传回服务器,验证资源是否已经修改,如果没有修改直接使用缓存.具体流程可以是如下情景:
基于内容的hash往往会比Last-modified更准确.
2. Last-modified
服务器端资源最后的修改时间,必须和 cache-control 共同使用,是检查服务器端资源是否更新的一种方式。当浏览器再次进行请求时,会向服务器传送 If-Modified-Since 报头,询问 Last-Modified 时间点之后资源是否被修改过。如果没有修改,则返回 304,使用缓存;如果修改过,则再次去服务器请求资源,返回200,重新请求资源。
3. Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和 Last-modified 结合使用. Expires 是 Web 服务器响应消息头字段,在响应 http 请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
4. Cache-Control的max-age
单位为秒,指定设置缓存最大的有效时间。当浏览器向服务器发送请求后,在 max-age 这段时间里浏览器就不会再向服务器发送请求了。以上就是浏览器缓存几个基本的概念,更多知识可以在wiki中学习,这里就不一一介绍了.接下来我们具体看看如何优化web应用以及缓存策略给公司带来的价值.
2.Web性能优化基本方案以及缓存策略为公司带来的价值
Web性能优化又是老生常谈的问题了,几年前就一直在探讨这个问题,笔者大致盘点一下性能优化的几个常用的方向:
1.资源的合并与压缩.
比如我们常用的gulp或者webpack这些打包工具, 可以帮我们压缩js,css,html代码,并且将不同页面模块的js,css打包合并到一个文件中,好处就是减少了http请求,降低了资源的体积,使得响应更快.但是仍然存在一个缺陷,就是合并代码会导致一次请求的资源体积会比之前分包的要大,所以会一定程度的影响页面渲染时间,所以这里需要做一个权衡,或者部分采用按需加载的方式.
2.图片压缩
一个网站往往更占资源的是媒体文件,比如图片,视频,音频等,对于图片在发布到线上时最好是需求提前压缩一下, 为了减少图片请求几年前常用的做法是雪碧图,也就是几张图片合成一张大图,通过背景定位来显示不同的图片,不过目前貌似用的不多了,现在更多的采用字体图标,svg,或者webp,所以我们需要根据不同的场景使用不同的策略,当然目前主流的云平台支持对象存储,对媒体资源有不错的优化,有条件的可以采用这种方案,比如七牛云,阿里的对象存储oss.
3. 合理规划html代码结构
这个优化主要是为了提高页面渲染时间,我们都知道css和js的加载一般都是阻塞的, css不会阻塞js和外部脚本的加载,但是会阻塞js的执行, 如果我们把css放到body最底部,那么我们在网络不好的情况下可能会看到先展示html文本然后才渲染页面样式的窘境,如果我们把js脚本放到head内,那么将会阻塞后面内容的渲染,并且造成一些应dom还未生成的导致的错误, 虽然我们可以采用async、defer让script变成异步的,但是如果不同js文件有依赖关系,那么很可能导致意外的错误,所以我们的最佳实践往往是如下这种结构的:
<html>
<head>
<title>趣谈前端</title>
<meta chars