浏览器的标签页会话机制,以及前进/后退缓存

对于浏览器来说,一个标签页就承载着一个标签页会话。

标签页会话

本文中所讲的session(会话)不是指客户端与服务端之间的会话:

  • 客户端与服务端之间的会话是指:为了完成某个目标,客户端与服务端进行的一系列通讯(请求/响应对)。在这种会话中,服务端需要通过某种机制来识别出当前的请求属于哪一个会话(比如cookie)。
  • 而本文所讲的标签页会话是指:在一个标签页的生命周期中,经历的一系列文档替换(卸载旧的文档,并加载新的文档)。文档替换过程中会发生的事件将总结在另一篇文章。
window.history就封装了标签页内部的标签页会话模型。

标签页会话的生命周期

  1. 打开一个新的标签页,用户就开始了一个新的会话。(如果有多个标签页同时打开,说明用户同时处于多个会话当中)
  2. 当用户修改标签页的URL,或点击当前页面上的超链接(且<a>target attribute为默认值_self),或提交表单时,就会发生一次文档替换(卸载当前document,加载新的document)。标签页会向会话历史(session history)中增加一个会话历史条目(session history entry)。

    如果URL的修改只是造成hashchange(或者通过JavaScript修改了hash),也会增加一个会话历史条目,不过不需要替换文档了。
    如果在JavaScript中调用了history.pushState(),也会增加一个会话历史条目,不过不需要替换文档了。类似地,history.replaceState()修改当前的会话历史条目,不替换文档。
  3. 用户可以通过浏览器的前进/后退按钮在会话历史条目之间迁移。大部分浏览器支持用户在前进/后退按钮上点击鼠标右键,查看可以迁移到哪些会话历史条目。
    浏览器的标签页会话机制,以及前进/后退缓存
  4. 有的浏览器(比如Firefox和Safari)还实现了Back-Forward Cache,从而能够更快地载入旧的会话历史条目。
  5. 通过ctrl+shift+t,用户能够恢复上一次关闭的标签页,以及它承载的会话历史。不过,Back-Forward Cache不会随着它的历史条目一起恢复,被恢复的历史条目的文档需要重新加载。

前进/后退缓存(Back-Forward Cache)

Firefox和Safari实现了Back-Forward Cache,在Webkit中它被称为Page Cache。关于它的详细信息可以查看参考资料1和2。我在这里想指出的是,Back-Forward Cache是与标签页会话机制深度结合的:

  • 缓存时机:当标签页即将从一个会话历史条目迁移到另一个时,且需要文档替换时(前面举过一些不需要文档替换的例子),如果旧的文档满足某些条件(比如存在unloadbeforeunload的监听器,其他条件列举在Using Firefox 1.5 caching - Mozilla | MDN),那么旧的文档不会被销毁,而是保留在内存中并暂停其活动。
  • 恢复时机:当用户通过浏览器的前进/后退按钮来载入旧的会话历史条目时,如果这个条目对应的文档有被缓存,那么直接从缓存中恢复这个文档并恢复其活动。

Back-Forward Cache的逻辑是:用户点击前进/后退按钮的时候,就是期待回到“之前看到过的那个页面”,所以浏览器不需要从服务器获取一份新的代码并重新加载页面。

关于Back-Forward Cache的讨论仅仅针对支持它的浏览器(Chrome不支持它)。

实验

<!DOCTYPE html>
<!-- test2.html -->
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Test</title>
</head>

<body>
  <a href="./test3.html">link</a>
  <script>
    console.log("loading");  // 这行只会在每次重新加载的时候打印
    window.addEventListener("pageshow", function (event) {
      // 这行会在每次重新加载、从缓存中恢复的时候打印
      console.log('pageshow', event.persisted, event);
    });
  </script>
</body>

</html>
<!DOCTYPE html>
<!-- test3.html -->
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Test</title>
</head>

<body>
  <a href="./test2.html">link</a>
  <script>
    console.log("loading");  // 这行只会在每次重新加载的时候打印
    window.addEventListener("pageshow", function (event) {
      // 这行会在每次重新加载、从缓存中恢复的时候打印
      console.log('pageshow', event.persisted, event);
    });
  </script>
</body>

</html>

步骤(使用Firefox):

  1. 先点击页面中的链接若干次,向标签页会话中增加历史条目。查看控制台,验证载入新文档的时候会输出"loading"和"pageshow"。
  2. 然后通过鼠标右键浏览器前进/后退按钮来查看前面/后面的会话历史条目。
  3. 选择旧的会话历史条目载入,查看控制台,验证载入新文档的时候只会输出"pageshow"而不会输出"loading",说明<script>并没有被重新执行,而是使用先前的DOM和JavaScript环境。
  4. 关闭标签页,再通过ctrl+shift+t恢复上次关闭的标签页,验证会话历史条目随着标签页一起被恢复了。加载先前的会话历史条目,发现文档没有从缓存中恢复而是重新加载,说明Back-Forward Cache在关闭标签页的时候被销毁了。

参考资料

  1. Using Firefox 1.5 caching - Mozilla | MDN
  2. WebKit Page Cache I – The Basics | WebKit
  3. Working with BFCache - Archive of obsolete content | MDN
  4. 7.1 Browsing contexts - HTML Standard 定义了标签页、browsing context、session history、Window、Document之间的关系
  5. 7.7 Session history and navigation - HTML Standard

相关推荐