🗒️History API
History API 操作浏览器的 session history,通过 History 对象。History 对象的引用可以通过 window 的只读属性 history
获取到,即 window.history
。
session history 不同于 browser history:
session history 是指加载当前页面的 tab 或 frame 访问过的页面
browser history 是指用户访问过的页面的时间记录
1. 接口定义
History 接口不继承任何方法,它的定义如下:
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'}
,/about
1
{page: 'Home Page'}
,/home
0
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