Memory Cache 与 Disk Cache 介绍

3,779 阅读7分钟

在了解它们之前,我们不妨先来段“自我介绍”。

Memory Cache

Memory Cache 翻译过来便是“内存缓存”,顾名思义,它是存储在浏览器内存中的。其优点为获取速度快、优先级高,从内存中获取资源耗时为 0 ms,而其缺点也显而易见,比如生命周期短,当网页关闭后内存就会释放,同时虽然内存非常高效,但它也受限制于计算机内存的大小,是有限的。

那么如果要存储大量的资源,这是还得用到磁盘缓存。

Disk Cache

Disk Cache 翻译过来是“磁盘缓存”的意思,它是存储在计算机硬盘中的一种缓存,它的优缺点与 Memory Cache 正好相反,比如优点是生命周期长,不触发删除操作则一直存在,而缺点则是获取资源的速度相对内存缓存较慢。

Disk Cache 会根据保存下来的资源的 HTTP 首部字段来判断它们是否需要重新请求,如果重新请求那便是强缓存的失效流程,否则便是生效流程。

从两者的优缺点中我们可以发现,Memory Cache 与 Disk Cache 珠联璧合,优势互补,共同构成了浏览器本地缓存的左右手。

介绍完这两位“老朋友”,接下来我们继续来了解下浏览器的缓存机制,因为目前市面上浏览器众多,不同浏览器的缓存机制都可能不同,笔者还是以主流的 Chrome 为例进行介绍。

浏览器缓存机制

浏览器缓存机制包含了 Http 缓存中强缓存、协商缓存的知识点,这里就不再进行赘述,下面主要介绍与 Memory Cache、 Disk Cache 相关的机制。

缓存获取顺序

按照缓存顺序来讲,当一个资源准备加载时,浏览器会根据其三级缓存原理进行判断。

  1. 浏览器会率先查找内存缓存,如果资源在内存中存在,那么直接从内存中加载
  2. 如果内存中没找到,接下去会去磁盘中查找,找到便从磁盘中获取
  3. 如果磁盘中也没有找到,那么就进行网络请求,并将请求后符合条件的资源存入内存和磁盘中

按照以上顺序,浏览器缓存与 HTTP 缓存才得以相辅相成,在有效的沟通和判断中尽可能的减少不必要的资源浪费。

缓存存储优先级

上述我们讲解了缓存资源的获取顺序,那么在获取之前,浏览器又是按照什么优先级来存储资源的?这一问题也可以直接换成“浏览器判断一个资源是存入内存缓存还是磁盘缓存的依据是什么?”。

其实答案在介绍内存缓存和磁盘缓存时已经有所涉及,我们以掘金首页为例子进行介绍。

当我们打开开发者工具并在浏览器输入 url 访问后,发现除了 base64 的图片永远从内存加载外,其他大部分资源会从磁盘加载。

20210914175010.jpg

磁盘缓存会将命中强缓存的 js、css、图片等资源都收入囊中,也省去我们担心它“挑食”的问题。

而内存缓存不这样,为了保持“苗条的身材”,它不得不控制“饮食”,尽可能的去挑选适合自己的“食物”。此时我们刷新下页面让内存缓存生效:

20210914220039.jpg

我们先过滤下只看 JS 资源的加载情况,发现有些被内存缓存了,有些则没有,这是为什么?

有些读者可能会猜测是不是没有被缓存的是因为资源比较大,其实不然,上方图片笔者圈出了 Initiator 列,通过该列便可以找到答案。

Initiator 列表示资源加载发起的位置,我们点击从内存获取资源的该列值后可以发现资源是在 HTML 渲染阶段就被加载的,如以下代码所示:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <title>Demo</title>
        <script src="https://i.snssdk.com/slardar/sdk.js"></script>
    </head>
    <body>
        <div id="cache">加载的 JS 资源大概率会存储到内存中</div>
    </body>
</html>

而被内存抛弃的资源我们也可以发现其都是异步加载的资源,这些资源没有被内存缓存,比如像这样:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <title>Demo</title>
    </head>
    <body>
        <div id="cache">异步加载的 JS 资源没有存储到内存中</div>
        <script>
            window.onload = function () {
                setTimeout(function () {
                    var s = document.createElement("script");
                    s.type = "text/javascript";
                    s.async = true;
                    s.src = "https://i.snssdk.com/slardar/sdk.js";
                    var x = document.getElementsByTagName("script")[0];
                    x.parentNode.insertBefore(s, x);
                }, 2000);
            };
        </script>
    </body>
</html>

根据以上测试代码很容易产生错误的判断结论:异步加载的 JS 资源不会存储到内存中。

其实这里异步函数只是起到了协助性的作用,并不是造成不存内存的根本性原因,就好比“羚羊遇见了狼,羚羊没有被狼吃了并不是因为羚羊会跑,而是因为其跑过了狼”。

我们可以把 JS 资源看作是“羚羊”,把浏览器内存看作是“狼”,“羚羊没有被狼吃了”便可以理解为异步加载的 JS 资源没有存储到内存中,此时“羚羊跑过了狼”就可以看作是异步资源的加载晚于浏览器内存的生效时间,最终笔者得出的结论便很好理解:

浏览器内存缓存生效的前提下,JS 资源的执行加载时间会影响其是否被内存缓存

我们可以修改上述的 setTimeout 时间为 1 秒后再次进行验证,大家会发现即使异步了,JS 资源还是很容易被内存缓存,原因便是异步 JS 资源加载时浏览器渲染进程可能还没有结束,而进程没结束就有被存入内存的可能。

此外图片资源(非 base64)也有和 JS 资源同样的现象,而 CSS 资源比较与众不同,其被磁盘缓存的概率远大于被内存缓存。

20210915153932.jpg

这一现象目前还没有找到标准的答案,网上给出的非标准解释是:

因为 CSS 文件加载一次就可渲染出来,我们不会频繁读取它,所以它不适合缓存到内存中,但是 JS 之类的脚本却随时可能会执行,如果脚本在磁盘当中,我们在执行脚本的时候需要从磁盘取到内存中来,这样 IO 开销就很大了,有可能导致浏览器失去响应。

以上所述的内存缓存(Memory Cache)在浏览器标准中并没有详尽的描述,笔者也是根据自身实践及总结得出的一些结论,不同的浏览器在加载资源时可能会所有差异,读者还需根据自己的理解和实践进行进一步探索。

Preload 与 Prefetch

基于上述现象的前提下,笔者还发现了与资源加载相关的两个功能(Preload 与 Prefetch)也会潜移默化的影响着浏览器缓存。

preload 也被称为预加载,其用于 link 标签中,可以指明哪些资源是在页面加载完成后即刻需要的,浏览器会在主渲染机制介入前预先加载这些资源,并不阻塞页面的初步渲染。例如:

<link rel="preload" href="https://i.snssdk.com/slardar/sdk.js" as="script" />

而当使用 preload 预加载资源后,笔者发现该资源一直会从磁盘缓存中读取,JS、CSS 及图片资源都有同样的表现,这主要还是和资源的渲染时机有关,在渲染机制还没有介入前的资源加载不会被内存缓存。

相反 prefetch 则表示预提取,告诉浏览器加载下一页面可能会用到的资源,浏览器会利用空闲状态进行下载并将资源存储到缓存中。

<link rel="prefetch" href="https://i.snssdk.com/slardar/sdk.js" />

使用 prefetch 加载的资源,刷新页面时大概率会从磁盘缓存中读取,如果跳转到使用它的页面,则直接会从磁盘中加载该资源。

利用好 preload 和 prefetch 这“两员大将”,我们可以优化浏览器资源加载的顺序和时机,在页面性能优化环节至关重要。

结语

相信阅读完本节内容,你会对 Memory Cache 与 Disk Cache 有了新的认识,虽然本文从异步资源的角度阐述了不同资源的缓存优先级,但至于资源到底会被存储在内存还是磁盘,浏览器有它自己的考量,而这一衡量标准并没有官方的文档说明,但“实践出真知”,只有亲自动手实操,才能体会浏览器的“良苦用心”。