对于浏览器来说,一个标签页就承载着一个标签页会话。
标签页会话
本文中所讲的session(会话)不是指客户端与服务端之间的会话:
- 客户端与服务端之间的会话是指:为了完成某个目标,客户端与服务端进行的一系列通讯(请求/响应对)。在这种会话中,服务端需要通过某种机制来识别出当前的请求属于哪一个会话(比如cookie)。
- 而本文所讲的标签页会话是指:在一个标签页的生命周期中,经历的一系列文档替换(卸载旧的文档,并加载新的文档)。文档替换过程中会发生的事件将总结在另一篇文章。
window.history
就封装了标签页内部的标签页会话模型。
标签页会话的生命周期
- 打开一个新的标签页,用户就开始了一个新的会话。(如果有多个标签页同时打开,说明用户同时处于多个会话当中)
-
当用户修改标签页的URL,或点击当前页面上的超链接(且
<a>
的target attribute为默认值_self
),或提交表单时,就会发生一次文档替换(卸载当前document,加载新的document)。标签页会向会话历史(session history)中增加一个会话历史条目(session history entry)。如果URL的修改只是造成hashchange(或者通过JavaScript修改了hash),也会增加一个会话历史条目,不过不需要替换文档了。
如果在JavaScript中调用了history.pushState(),也会增加一个会话历史条目,不过不需要替换文档了。类似地,history.replaceState()
会修改当前的会话历史条目,不替换文档。 - 用户可以通过浏览器的前进/后退按钮在会话历史条目之间迁移。大部分浏览器支持用户在前进/后退按钮上点击鼠标右键,查看可以迁移到哪些会话历史条目。
- 有的浏览器(比如Firefox和Safari)还实现了Back-Forward Cache,从而能够更快地载入旧的会话历史条目。
- 通过
ctrl+shift+t
,用户能够恢复上一次关闭的标签页,以及它承载的会话历史。不过,Back-Forward Cache不会随着它的历史条目一起恢复,被恢复的历史条目的文档需要重新加载。
前进/后退缓存(Back-Forward Cache)
Firefox和Safari实现了Back-Forward Cache,在Webkit中它被称为Page Cache。关于它的详细信息可以查看参考资料1和2。我在这里想指出的是,Back-Forward Cache是与标签页会话机制深度结合的:
- 缓存时机:当标签页即将从一个会话历史条目迁移到另一个时,且需要文档替换时(前面举过一些不需要文档替换的例子),如果旧的文档满足某些条件(比如存在
unload
或beforeunload
的监听器,其他条件列举在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):
- 先点击页面中的链接若干次,向标签页会话中增加历史条目。查看控制台,验证载入新文档的时候会输出"loading"和"pageshow"。
- 然后通过鼠标右键浏览器前进/后退按钮来查看前面/后面的会话历史条目。
- 选择旧的会话历史条目载入,查看控制台,验证载入新文档的时候只会输出"pageshow"而不会输出"loading",说明
<script>
并没有被重新执行,而是使用先前的DOM和JavaScript环境。 - 关闭标签页,再通过
ctrl+shift+t
恢复上次关闭的标签页,验证会话历史条目随着标签页一起被恢复了。加载先前的会话历史条目,发现文档没有从缓存中恢复而是重新加载,说明Back-Forward Cache在关闭标签页的时候被销毁了。
参考资料
- Using Firefox 1.5 caching - Mozilla | MDN
- WebKit Page Cache I – The Basics | WebKit
- Working with BFCache - Archive of obsolete content | MDN
- 7.1 Browsing contexts - HTML Standard 定义了标签页、browsing context、session history、Window、Document之间的关系
- 7.7 Session history and navigation - HTML Standard
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。