You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
在错纵复杂的网络环境下,如何将页面快速得传递给用户是前端们的职责,而在此之后,如何减少网络传输的花费同样值得我们关注。本文以 HTTP/1.x 和 Service Worker 缓存两个方面,就如何减少网络传输成本为目标,探讨下笔者最近对于缓存的实践,权当抛砖引玉 🤪
HTTP 缓存
The performance of web sites and applications can be significantly improved by reusing previously fetched resources. Web caches reduce latency and network traffic and thus lessen the time needed to display a representation of a resource. By making use of HTTP caching, Web sites become more responsive.
根据 MDN 定义可知道,缓存是对已获取资源的重新利用,是提升 WEB 性能的重要指标。根据是否和 Server 进行交互,HTTP 缓存分为两类:
强制缓存
协商缓存
强制缓存是无需和 Server 进行交互,直接在 Client 进行缓存。 而协商缓存需要和 Server 交互来判断是否重用缓存。
constcontroller=newAbortController()constsignal=controller.signalfetch('/some/url',{ signal }).then(res=>res.json()).then(data=>{// do something with "data"}).catch(err=>{if(err.name=='AbortError'){return}})// controller.abort(); // can be called at any time
增加 Service Worker 开关
Service Worker 提供的缓存虽然好用,但有时候需要根据业务注销 Service Worker, 这时就需要一个开关来控制。而且应该在第一次部署的时候就增加开关,对于缓存进行控制。
fetch(API.switch).then(res=>{constisOn=res.statusif(isOn){sw.register()}else{sw.unregister()}}).catch(err=>{console.error('fetch sw status error',err)})
HTTP 缓存
根据 MDN 定义可知道,缓存是对已获取资源的重新利用,是提升 WEB 性能的重要指标。根据是否和 Server 进行交互,HTTP 缓存分为两类:
强制缓存是无需和 Server 进行交互,直接在 Client 进行缓存。 而协商缓存需要和 Server 交互来判断是否重用缓存。
HTTP 缓存首部有以下几种:
Expires
Cache-Control
ETag/If-None-Match
Last-Modified/If-Modified-Since
Expires
Expires
通过设置一个时间戳,控制缓存的过期时间点。但缺点是客户端时间和服务器时间可能不一致,无法保证缓存的同步性。此外,如果存在
Cache-Control
首部并设置了max-age
指令,Expires
首部将被忽略。Cache-Control
具体配置细节见 MDN,属于强制缓存,不再赘述。这里只讲下自己实践所用到的设置项。
public | private
max-age=<seconds>
no-cache | no-store | must-revalidate
public
和private
定义了缓存的共享性,分为共享(public)与私有(private)缓存。共享缓存存储的响应能够被多个用户使用,私有缓存只能用于单独用户。 共享缓存可存在于 ISP、网关或 CDN 的节点上,能很大程度缓存热门资源,减少网络拥堵与延迟,但存在中间人攻击的风险,故存在private
缓存 —— 只缓存在用户的浏览器端,不会被共享。可根据自己的业务需求,选择是私有还是共享的。max-age=<seconds>
规定了缓存时长,以秒为单位。从开始接收到资源为时间点,在接下来的max-age
时间内使用缓存。理论上来说可以长期缓存,但带来的问题是浏览器缓存的臃肿,根据 RFC2616 最长时常设为一年较为合适,即Cache-Control: max-age=31536000
。no-cache
、no-store
和must-revalidate
。no-cache
规定使用缓存之前时一定要经过验证,比如验证ETag/ Last-Modified
等;no-store
直接禁止浏览器以及所有中间缓存存储任何版本的返回响应,每次用户请求该资产时,都会向服务器发送请求,并下载完整的响应;must-revalidate
缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。ETag/If-None-Match
ETag
是对资源的一个特殊标志符,能唯一确定资源。语法:W/
表明了资源是否采用弱类型验证器进行比较,其较为容易生成但不利于比较。"<etag_value>"
是对资源的唯一标志符,其值是一串 ASCII 字符串。生成规则没有一定的要求,但常采用的生成算法是内容的 hash 值加上内容的最后修改时间。当响应头部包含
ETag
时,下次请求时浏览器会自动带上If-None-Match: <last_etag_value>
首部,用来验证资源是否过期。 如果已过期,则以HTTP 200
返回新的内容响应并带上新的ETag
。如果资源未过期,则返回HTTP 304
告知浏览器资源未过期可以继续使用。Last-Modified/If-Modified-Since
顾名思义,
Last-Modified/If-Modified-Since
是根据内容最后的修改时间来判断是否采用缓存的方法。但由于最小时间单位为秒,对于要求时间比较精细的资源可能不太适用。缓存优先级
HTTP/1.x 缓存首部的优先级:
Cache-Control
>Expires
>ETag/If-None-Match
>Last-Modified/If-Modified-Since
, 即在同时设定了上述首部时Cache-Control
最高,可根据业务需求设定。以上,便是 HTTP/1.x 缓存设置的首部解释,可以通过Browser Caching Checker 对浏览器缓存进行检查。
Server Worker 缓存
当下时间点,Service Worker 在浏览器上的支持度已高达 86.16%, 所以是时候考虑开启 Service Worker 来加速你的网站了。不仅可以利用 Service Worker 所带来的缓存好处,还能很容易迁移到 PWA,更大程度发掘 Web App 的能力。
不同于 HTTP 缓存,Server Worker 不仅能动态缓存资源,而且还能提供 offline 模式,对弱网络环境的用户极为友好。开启 Service Worker 大概需要注册、安装、缓存资源、更新和注销等过程。
接下来以一个小 Demo 为例,简单介绍如何开启一个 Service Worker 服务。源码见 sw-cache-example
注册
注册流程很简单,只需要判断浏览器是否支持 Service Worker 特性,并在页面 Load 之后,注册 Service Worker 服务,关键代码:
安装
安装过程需要做的有:监听
install
事件,并在其回调事件内缓存资源。响应缓存
最重要的一步,就是在资源被缓存后利用缓存了。需要做的也很简单:监听
fetch
事件 -> 对已缓存的资源进行响应。更新
更新也是 Service Worker 很重要的一步,其过程也很易懂:验证资源是否过期 -> 对过期的资源进行删除并缓存新的资源。
注销
注销只需要拿到 Service Worker 实例,调用
unregister
即可。至此,基本完成了 Service Worker 的基本部署,开启其提供的缓存能力。
😷 实践过程中遇到的坑
fetch
由于在响应缓存时,需要通过监听
fetch
事件来响应缓存,故需要更改 HTTP 请求方法为fetch
,其 API 参见 MDN。 对于不支持fetch
的浏览器,可以使用这个 fetch 进行打补丁。fetch
请求由于
fetch
没有提供原生的取消方法,故需要使用 signal 来取消fetch
请求。Polyfill 参照 abortcontroller-polyfill
Service Worker 提供的缓存虽然好用,但有时候需要根据业务注销 Service Worker, 这时就需要一个开关来控制。而且应该在第一次部署的时候就增加开关,对于缓存进行控制。
对于一般的 SPA,是通过入口文件进行资源的索引,所以对入口文件应该不予缓存,并要求其强制更新。在使用sw-precache-webpack-plugin应排除入口文件:
对入口文件可以设置 HTTP 响应首部:
其含义是不使用本地及任何中间存储缓存,必须和服务器取得验证才能拿到新的内容。
总结
Cache-Control
对静态资源进行长期缓存,配合 webpack 打包生成的文件 hash 名,可全部采用这一策略ETag/If-None-Match
对内容 hash 进行精确缓存Last-Modified/If-Modified-Since
对修改时间对内容进行缓存,以替代使用ETag/If-None-Match
对 CPU 的高消耗Service Worker
提供动态缓存和离线能力所以,现在开始打开调试工具,为你的网站增加缓存吧~ ✌️
Reference
The text was updated successfully, but these errors were encountered: