🗒️History API
History API 操作浏览器的 session history,通过 History 对象。History 对象的引用可以通过 window 的只读属性 history 获取到,即 window.history。
session history 不同于 browser history:
session history 是指加载当前页面的 tab 或 frame 访问过的页面
browser history 是指用户访问过的页面的时间记录
1. 接口定义
History 接口不继承任何方法,它的定义如下:
enum ScrollRestoration { "auto", "manual" };
[Exposed=Window]
interface History {
readonly attribute unsigned long length;
attribute ScrollRestoration scrollRestoration; // 允许 Web 应用显式设置默认的滚动恢复行为
readonly attribute any state;
undefined go(optional long delta = 0);
undefined back();
undefined forward();
undefined pushState(any data, DOMString unused, optional USVString? url = null);
undefined replaceState(any data, DOMString unused, optional USVString? url = null);
};2. 两个只读属性
history.length表示在 history stack 里的 pages 的数量其初始值是 1,包括当前加载的页面
history.state表示 history stack 栈顶的那个 state 对象其初始值是
null,除非开始调用了pushState()或replaceState()

3. 前进和后退
可以使用以下三个方法在 session history 中前进和后退:
history.back():后退history.forward():前进history.go(num):从 session history 里加载特定页面位置相对于当前 page,正数表前进、负数表后退
浏览器兼容性:IE 要求是个字符串,而不是整数
执行这三个方法时,浏览器地址栏里的 URL 会变,同时也会触发 popstate 事件。如果越界了,这三个方法不会产生任何效果,也不会报错和抛异常。

这三个方法都是异步的,可以通过监听 popstate 事件来判断导航何时完成。
4. 维护 history 栈
要操作浏览器 session history 栈,有两个方法:
pushState(), add a history entry,添加一条 history 条目replaceState(), modify the history entry,修改 history 条目修改当前的 history 条目,将其替换为传入的
state对象和 URL该方法适用于处理对用户行为的响应
4.1 参数说明
参数
state:是一个可序列化的 JavaScript 对象state对象序列化后的大小不能超过 16 MiB,因为 Firefox 会将state对象保存到用户的磁盘中,以便在用户重启浏览器后可以恢复它们如果大小超了,方法会抛出异常
如果真的需要大的存储空间,则可以使用
sessionStorage或localStorage
参数
unused:因为历史原因不能为空,通常会传一个空字符串历史上表示页面的标题,目前除 Safari 之外所有的浏览器都忽略了该参数
建议传个空字符串,以向后兼容
参数
url:是新 history 条目的 URL新 URL 必须和当前 URL 同源,否则方法会抛出异常
新 URL 可以是绝对的,也可以是相对的(相对当前 URL)
如果该参数未指定,默认是文档的当前 URL
浏览器只改变地址栏里的 URL,并不会 reload 页面
4.2 只替换 URL
调用 pushState() 和 replaceState() 这两个方法,只会替换浏览器地址栏里的 URL,并不会触发 popstate 事件。
eg1. 页面初始化时调用 init()

eg2. 响应页面上按钮的 click 事件


4.3 pushState() 的位置
pushState() 的位置需要注意的是,pushState() 是直接在 stack 当前位置的后面插入的。
如果当前位置正是栈顶,那么就直接入栈,此时 length 会增加 1
否则先清空当前位置(不含)上面的所有元素,然后再入栈,此时 length 的长度取决于当前位置
将这种入栈逻辑对应到【点击浏览器的前进+后退箭头】+【在相同页签打开当前网页中的超链接】,就会觉得还算符合应用场景。
来看个例子感受下。
在页面执行了 init() 方法之后,此时 session history stack 的长度是 3,信息如下:
2
{page: 'About Page'},/about1
{page: 'Home Page'},/home0
null
初始的 history 如下图蓝框中标出的,位置在 2-/about。
执行了两次 history.back() 之后,位置在 0-null,如下图绿框中标出的。
此时点击了 pushState 按钮,位置 1 的内容变成了 {page: 'Push Test'}, /push-test,而 history stack 的长度变成了 2。如下图红框中标出的。

其它入栈场景,可自行测试(比如连续入栈相同的、比如要入栈的和下一个是相同的),结论都如上所述——只和是否在栈顶有关,又或者是一律先清空当前位置(不含)上面的栈元素,再入栈新元素。
4.4 replaceState()
replaceState()相比 pushState() 方法的逻辑,replaceState() 的逻辑就直观了很多:直接替换当前的。
5. popstate 事件
popstate 事件5.1 触发条件
以下两个条件必须同时满足:
触发时机:当用户在 session history 栈里来回导航时触发(即便连续的两条记录的内容是相同的)
当 session history 栈里连续的两条记录的内容相同时,来回切换也会触发 popstate 事件。比如先连续 push 三条,然后再点击浏览器的回退按钮,此时依然会触发 popstate 事件:

5.2 写法
event.state 是个只读属性,是提供给 pushState() 或 replaceState() 方法的 state 参数的拷贝。
5.3 发送时机
在浏览器可能触发 popstate 事件的情况下,遵循的步骤大约是:
如果新条目不包含现有的 Document,那么浏览器会先拉取内容并创建其
Document,即发送DOMContentLoaded和load事件,同时也会进行下面的步骤处理当前条目
如果当前条目的标题不是通过 History API 设置的,那么就将其设置为
document.title属性返回的字符串如果当前条目有持久的用户状态(persisted user state)要保存,那么在离开它之前,浏览器会把那些信息和当前条目一起存储,比如文档的滚动位置、表单 inputs 的值等其它类似数据
如果新条目和当前条目的 Document 对象不同,则
document属性要指向新条目的新条目
Document里的所有表单控件,对于有autocomplete的会自动完成配置如果新条目的文档已经完全加载并准备就绪(即
readyState是complete)并且该文档还不可见,那么就将其变为可见,然后在PageTransitionEvent的persisted属性是true的文档中触发pageshow事件
把文档的 URL 设置成新条目的 URL
如果是在启用替换的情况下执行 history 遍历,那么就从 history 中删除目标条目之前的那条
如果新条目没有持久的用户状态,并且它的 URL 片段不为空,则文档将滚动到该片段
将当前条目设置为新条目
如果新条目有一起保存的序列化
state信息,那么该信息会被反序列化为History.state,否则,state为null发送
popstate事件:如果state的值变了,那么就会发送popstate事件恢复持久化用户状态信息
发送
hashchange事件:如果原始条目和新条目共享同一个文档,但它们的 URL 中有不同的片段,则会发送hashchange事件

6. 主要参考
Last updated