设为首页 - 加入收藏
广告 1000x90
您的当前位置:144177黄大仙有求必应 > 静态缓冲 > 正文

Web静态资源缓存及优化

来源:未知 编辑:admin 时间:2019-05-12

  建立在资源加载速度之上,但不同资源类型的加载顺序和时机也会对其产生影响,所以也留给了我们更多的优化空间。

  当然除了速度,缓存还有另外2大功效,减少用户请求的带宽和减少服务器压力。

  对于前端而言,这可能是我们最容易忽略的缓存类型,原因在于大部分设置都在服务器运维层面上进行,不属于前端开发的维护范围。但静态资源的内容更新时机其实前端是最清楚的,如果能在理解浏览器缓存策略的基础上合理配置效果最佳。

  浏览器缓存策略一般通过资源的Response Header来定义,html文件在很早之前的规范里也可以通过Meta标签的http-equiv来定义。

  可在w3c的官方文档中查看所有HTTP Response Header字段的定义,跟缓存相关的主要有上图中被圈出来的几个:

  private:默认值,响应只能够作为私有的缓存(e.g., 在一个浏览器中),不能再用户间共享;

  max-age:数值,单位是秒,从请求时间开始到过期时间之间的秒数。基于请求时间(Date字段)的相对时间间隔,而不是绝对过期时间;

  Expires:指定了在浏览器上缓冲存储的页距过期还有多少时间,等同Cache-control中的max-age的效果,如果同时存在,则被Cache-Control的max-age覆盖。若把其值设置为0,则表示页面立即过期。并且若此属性在页面当中被设置了多次,则取其最小值。

  注:这个规则允许源服务器,对于一个给定响应,向 HTTP/1.1(或之后)缓存比 HTTP/1.0 提供一个更长的过期时间。

  Last-Modified/If-Modified-Since:本地文件在服务器上的最后一次修改时间。缓存过期时把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比,如果时间一致,那么返回304,客户端就直接使用本地缓存文件。

  Etag/If-None-Match:(EntityTags)是URL的tag,用来标示URL对象是否改变,一般为资源实体的哈希值。和Last-Modified类似,如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。Etag的优先级高于Last-Modified,Etag主要为了解决Last-Modified无法解决的一些问题。

  本地缓存过期后,浏览器会像服务器发送请求,request中会携带以下两个字段:

  其中在图右侧的“file modified?”判断中,服务器会读取请求头这两个值,判断出客户端缓存的资源是否最新,如果是的话服务器就会返回HTTP/304 Not Modified响应头,但没有响应体。客户端收到304响应后,就会从缓存中读取对应的资源;否则返回HTTP/200和响应体。

  meta是html语言head区的一个辅助性标签,其中的http-equiv字段定义了服务器和用户代理的一些行为。在之前的规范中,meta的http-equiv字段中有以下值与http header缓存相关的字段功能类似。

  其实也很好理解,写在meta标签中代表必须解析读取html的内容,但代理服务器是不会去读取的。大多浏览器已经不再支持,会忽略这样的写法,所以缓存还是通过HTTP headers去设置。

  Application Cache是html5引入的本地存储方案之一,可以构建离线-外其他浏览器均支持。

  application cache是通过mannifest文件来管理的,manifest文件是简单的文本文件,内容是需要被缓存供离线使用的文件列表,及不需要被缓存或读取缓存失败的文件控制。

  CACHE:需要缓存的资源文件,浏览器会自动缓存带有manifest属性的html页面;

  mannifest文件可以使用任意拓展名,但需要在服务器中添加MIME类型匹配,使用apache比较简单,如果使用.manifest作为拓展名在apache配置文件中添加。

  注:千万不要把manifest文件本身放在缓存文件列表中,不然浏览器无法更新manifest文件文件,最好在manifest文件的http headers中设置其立即过期。

  如果资源没有被缓存,而又没有设置NETWORK的情况下,将会无法加载,所以Network中必须使用通配符配置;

  缓存更新后第一次只能加载manifest文件,其他静态资源需要第二次加载才能看到最新效果;

  缓存文件清单中的文件本身更新浏览器是不会重新缓存,那怎么告诉浏览器缓存需要更新了呢?

  没错,这就是官方建议替代Application Cache的方案。早在2014年,W3C就公布了Service Worker的草案。它作为一个独立的线程,是一段在后台运行的脚本。它的出现使得web app也可以具有类似native app的离线使用、消息推送、后台自动更新等能力。

  虽然现在其浏览器支持情况并不是很广泛,但以后应该会大面积支持。本文做简单介绍,具体使用方法可以参考官方文档《The Offline Cookbook》。

  1、首先,要使用Service Worker,需要添加一个Service Worker的js的文件,然后在我们的html页面中注册对这个文件的引用。

  2、其次,我们在js文件中补充Service Worker的生命周期事件。Service Worker生命周期有三部曲:注册,安装和激活。

  对于获取文件和缓存文件,Service worker依赖了两个 API:Fetch(通过网络重新获取内容的标准方式) 和Cache(应用数据的内容存储,此缓存独立于浏览器缓存和网络状态)。

  React脚手架create-react-app中已经内置了PWA功能,我们来看下打包后的build文件夹下的文件结构:

  以注册文件为service-worker.js为例,每次访问ServiceWorker控制的页面,浏览器都会加载最新的service-worker.js文件,跟当前service-worker.js文件对比,只要内容有任何不同,浏览器都会获取并安装新文件。但是不会立即生效,原有的ServiceWorker还是会运行,只有当ServiceWorker控制的页面全部关闭后,新的ServiceWorker才会被激活。

  LocalStorage虽是浏览器端缓存一种,但有多少人会用它来缓存文件呢?首先缓存读取需要依靠js的执行,所以前提条件就是能够读取到html及js代码段;其次文件的版本更新控制会带来更多的代码层面的维护成本,所以LocalStorage更适合关键的业务数据而非静态资源。

  客户端浏览器先检查是否有本地缓存是否过期,如果过期,则向CDN边缘节点发起请求,CDN边缘节点会检测用户请求数据的缓存是否过期,如果没有过期,则直接响应用户请求,此时一个完成HTTP请求结束;如果数据已经过期,那么CDN还需要向源站发出回源请求。

  CDN边缘节点缓存策略因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的Cache-control: max-age的字段来设置CDN边缘节点数据缓存时间。另外可通过CDN服务商提供的“刷新缓存”接口来更新缓存。

  通过prebrowsing可以提前缓存部分文件,可作为一种静态资源加载优化的手段。prebrowsing有以下几种:

  dns-prefetch:DNS预解析,告诉浏览器未来我们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。多在使用第三方资源时使用。

  preconnect:预连接,完成 DNS 预解析同时还将进行 TCP 握手和建立传输层协议。

  prerender:预渲染,预先加载文档的所有资源,类似于在一个隐藏的 tab 页中打开了某个链接 – 将下载所有资源、创建 DOM 结构、完成页面布局、应用 CSS 样式和执行 Java 脚本等。

  prefetch:预获取,使用 prefetch 声明的资源是对浏览器的提示,暗示该资源可能『未来』会被用到,适用于对可能跳转到的其他路由页面进行资源缓存。被 prefetch 的资源的加载时机由浏览器决定,一般来说优先级较低,会在浏览器『空闲』时进行下载。

  preload:预加载,主动通知浏览器获取本页的关键资源,只是预加载,加载资源后并不会执行;

  对于前面三种不少浏览器已经内部默认做了优化,而prefetch & preload需要开发者根据情况代码手动设置。

  从prefetch和preload的浏览器支持情况来看,prefetch除了safari外基本浏览器都有所支持,但preload作为新出的规范,兼容性差些,但safari正慢慢支持这一标准,如在iOS的safari高级选项的试验性Webkit功能中已经有Link Preload这一选项。

  preload 是声明式的 fetch,可以强制浏览器请求资源,同时不阻塞文档事件,是对浏览器指示预先请求当前页需要的资源(关键的脚本,字体,主要图片)。

  prefetch 提示浏览器这个资源将来可能需要,但是把决定是否和什么时间加载这个资源的决定权交给浏览器。prefetch 应用场景稍微有些不同 —— 用户将来可能在其他部分(比如视图或页面)使用到的资源。

  注:prebrowsing 好用但千万不要乱用,除非你非常明确会加载要prebrowsing的文件,不然会加重浏览器负担适得其反。

  接触过Next.js的同学都知道,next.js提供了一个具有预获取功能的模块:next/prefetch,看起来功能与prefetch类似,但其优先级与preload类似。

  由于features链接设置了prefetch,访问Index页面时浏览器会在页面加载完毕后从服务器取feature.js的文件,在index页面访问features页面时不会再从服务器请求features.js文件,直接从本地缓存中读取;contact没有做处理,从index访问contact时会从服务器请求concact.js文件。

  虽然大多数html只会在每次发布上线时才会改变,如更新js/css资源的引用地址,所以一般将HTTP Headers中设置一个比较短的max-age值,如cache-control: max-age=300,除此之外建议服务器开启Etag。

  但以实时内容为主的网站(如金融类)为了页面的打开速度,会采取后台服务生产的方式 ,将所有首页数据全部生成到html中,省去用户首次加载时的后台接口请求等待时间。一般会设置cache-control: no-cache。

  现在一般都通过文件名进行版本控制。Webpack打包命名可根据文件内容生成文件名的hash值,每次打包只有当内容改才重新生成hash值。此种情况之下,可以在HTTP Headers设置一个较大的缓存时间,如max-age=2592000,尽量避免304请求和服务器进行请求连接。

  如果在浏览器发现需要加载font文件之前就告诉浏览器下载font文件,会加快文件下载和页面加载速度。

  其实不同浏览器下载font文件的时间不太一样,有的碰到css的声明就会加载,有的会等到dom节点匹配css声明时加载。

  所有的静态资源文件通过Service Worker进行缓存控制和离线化加载,示范如上不再赘述;

  这个单页页面会打开几个小的页面(红色圈部分),通过webpack打包之后大概这个样子:

  其中第一个index.js会在页面初次加载,其他4个js会在路由切换时动态加载。考虑下这个页面的业务场景,只要进入到这个页面,其他几个路由是一定会访问到的。所以如果在页面加载完成之后,趁户思考之际就主动把剩下几个js加载好,岂不完美。

  在此选用了preload-webpack-plugin这个插件,它可以打包将动态路由进行预加载。

  访问页面可以看到,在不影响dom加载的情况下,浏览器预先加载了另外几个后面将会用到的js,当切换到对应路由时,也会直接从缓存取,不从服务器请求资源。

  非动态加载(路由)页面的css会单独打包,在html文件中进行引用。除了使用一些打包插件优化代码体积外,可将css更细粒度拆分,如首页的css+弹窗css+页面标签切换的css等。除首页css外的先预加载,然后动态获取。但一般来说一个页面的css大小在合理的代码情况下经过gzip压缩后都不会过大,所以优化的效果并不会太明显。

  动态加载路由中css没有单独拆分而是在路由的js中,所以只能随着js优化了。

  对于font文件,除了减少文件大小,设置缓存时间之外,也可以通过预加载的方式提前让浏览器下载来提高首屏渲染速度。预加载webfont需要与webpack的html-webpack-plugin结合,打包时将制定的字体插入到html中。网上找了一圈没有找到现成的插件,自己来写一个。

本文链接:http://that-a-way.com/jingtaihuanchong/64.html

相关推荐:

网友评论:

栏目分类

现金彩票 联系QQ:24498872301 邮箱:24498872301@qq.com

Copyright © 2002-2011 DEDECMS. 现金彩票 版权所有 Power by DedeCms

Top