预加载系列二:让File Prefetching丝丝润滑无痛无痒

预加载系列二:让File Prefetching丝丝润滑无痛无痒_第1张图片

所谓 File Prefetching 就是在一个页面加载成功后,默默去预加载后续可能会被访问到的页面的资源。
前端资源预加载其实没啥新鲜的,我们倒腾这个事情的过程却是很有有意思也很有启发性。

第一个版本,简单粗暴有点痛

1、建一个独立的页面,里面索引了各种需要预加载的css、js,代码类似下面这样。



    
    ...其他需要预加载的css



    
    ...其他需要预加载的js

2、 在每个页面加入一个iframe(一般通过base模板统一加),这样每个页面打开的时候都会加载上面这个页面。假设上面的页面的url是 https://xxx.com/common/prefetching.html 那么我们每个页面底部都有这么一行代码:

如何验证

要验证某个file prefetching的方案是否真的有效,无非就是以下几步:
(假设A页面使用了showcase_d0fbaaef124a8691398704216ccd469a.css,而B页面不会)

  1. 让chrome终端打开的时候cache功能依旧有效

  2. 清空所有本地cache

  3. 打开B页面,在控制台Networking里看prefetching.html以及附属的资源文件是否被下载了

  4. 打开A页面,注意:是在地址栏里输入A的网址然后回车,不要打开A页面后习惯性地按Command/Ctrl+R来刷新,不出意外,我们会看到如下图这样的结果:

-------------------.png

这说明,这2个css文件是从cache里读的。如果Command/Ctrl+R来刷新页面,我们会看到这样的结果:

预加载系列二:让File Prefetching丝丝润滑无痛无痒_第2张图片

两者的差别是,Command/Ctrl+R的时候,浏览器会从cache里找该静态文件,如果找到了,会根据上次请求这个文件时得到的cache-control信息判断该静态文件是否已经过期了,如果没有,会以 if-modified-sinceEtag 等信息作为 request headers 向服务器请求这个文件,服务器如果认为文件没有变过,会返回Http code为304,浏览器于是直接读cache。具体不展开啦,可以看 [《HTTP caching
》](https://developers.google.com/web/fundam...《Understanding HTTP/304 Responses》

操作指引

让chrome终端打开的时候cache功能依旧有效:Chrome终端的配置里把Disable cache (while DevTools is open)的勾选去掉
清空所有cache:地址栏里输入 chrome://settings/clearBrowserData 打开后勾上 Cached images and filesClear browsing data
查看浏览器当前cache的资源列表chrome://cache/

第二个版本,依样画葫芦

目前看来,上面这个 File Prefeching 的方案是有效的。不过这种是最简陋的试验版,存在几个问题:

  1. prefetching.html 里的js会被执行,然后不可避免地会有一堆js错误 —— 看着难受~

  2. 通过iframe 加载 prefetching.html 会影响到当前页面相关资源的加载速度

  3. 每次打开页面都会加载一次 prefetching.html,虽然里面的静态文件都已经在第一次打开的时候被cache住了不会重复下载,但无谓多一个请求终究是没必要。

于是,我们上线使用的版本是这样的:

1、有一段每个页面都会被执行到的js:

// 打开一个iframe,下载之后页面可能需要的js/css
setTimeout(function() {
    var lastOpenTime = 0;
    var nowTime = (new Date()).getTime();
    try {
        lastOpenTime = window.localStorage.getItem('staticIframeOpenTime');
    } catch (e) {}

    if (lastOpenTime > 0 && (nowTime - lastOpenTime < 24 * 3600 * 1000)) {
        // 24小时打开一次iframe
        return;
    }

    var iframe = $('