History API 操作浏览器的 session history,通过 History 对象。History 对象的引用可以通过 window 的只读属性 history 获取到,即 window.history。
session historyarrow-up-right 不同于 browser historyarrow-up-right :
session history 是指加载当前页面的 tab 或 frame 访问过的页面
browser history 是指用户访问过的页面的时间记录
History 接口不继承任何方法,它的定义如下:
Copy 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 );
}; history.length 表示在 history stack 里的 pages 的数量
history.state 表示 history stack 栈顶的那个 state 对象
其初始值是 null,除非开始调用了 pushState() 或 replaceState()
可以使用以下三个方法在 session history 中前进和后退:
history.go(num):从 session history 里加载特定页面
执行这三个方法时,浏览器地址栏里的 URL 会变,同时也会触发 popstate 事件。如果越界了,这三个方法不会产生任何效果,也不会报错和抛异常。
这三个方法都是异步的,可以通过监听 popstate 事件来判断导航何时完成。
4. 维护 history 栈
要操作浏览器 session history 栈,有两个方法:
pushState(), add a history entry,添加一条 history 条目
replaceState(), modify the history entry,修改 history 条目
修改当前的 history 条目,将其替换为传入的 state 对象和 URL
参数 state:是一个可序列化的 JavaScript 对象
state 对象序列化后的大小不能超过 16 MiB,因为 Firefox 会将 state 对象保存到用户的磁盘中,以便在用户重启浏览器后可以恢复它们
如果真的需要大的存储空间,则可以使用 sessionStorage 或 localStorage
参数 unused:因为历史原因不能为空,通常会传一个空字符串
参数 url:是新 history 条目的 URL
新 URL 必须和当前 URL 同源,否则方法会抛出异常
新 URL 可以是绝对的,也可以是相对的(相对当前 URL)
浏览器只改变地址栏里的 URL,并不会 reload 页面
调用 pushState() 和 replaceState() 这两个方法,只会替换浏览器地址栏里的 URL,并不会触发 popstate 事件。
eg1. 页面初始化时调用 init()
eg2. 响应页面上按钮的 click 事件
4.3 pushState() 的位置
需要注意的是,pushState() 是直接在 stack 当前位置的后面插入的。
如果当前位置正是栈顶,那么就直接入栈,此时 length 会增加 1
否则先清空当前位置(不含)上面的所有元素,然后再入栈,此时 length 的长度取决于当前位置
将这种入栈逻辑对应到【点击浏览器的前进+后退箭头】+【在相同页签打开当前网页中的超链接】,就会觉得还算符合应用场景。
来看个例子感受下。
在页面执行了 init() 方法之后,此时 session history stack 的长度是 3,信息如下:
2 {page: 'About Page'}, /about
1 {page: 'Home Page'}, /home
初始的 history 如下图蓝框中标出的,位置在 2-/about。
执行了两次 history.back() 之后,位置在 0-null,如下图绿框中标出的。
此时点击了 pushState 按钮,位置 1 的内容变成了 {page: 'Push Test'}, /push-test,而 history stack 的长度变成了 2。如下图红框中标出的。
其它入栈场景,可自行测试(比如连续入栈相同的、比如要入栈的和下一个是相同的),结论都如上所述——只和是否在栈顶有关,又或者是一律先清空当前位置(不含)上面的栈元素,再入栈新元素。
4.4 replaceState()
相比 pushState() 方法的逻辑,replaceState() 的逻辑就直观了很多:直接替换当前的。
以下两个条件必须同时满足:
触发时机:当用户在 session history 栈里来回导航时触发(即便连续的两条记录的内容是相同的)
触发操作:只能是点击浏览器上的前进/后退的按钮或是调用相应的三个方法 (stack 未越界)
而执行 pushState(), replaceState() 方法并不会触发该事件 ,虽然它两也会改变 the active history 条目
当 session history 栈里连续的两条记录的内容相同时,来回切换也会触发 popstate 事件。比如先连续 push 三条,然后再点击浏览器的回退按钮,此时依然会触发 popstate 事件:
event.state 是个只读属性,是提供给 pushState() 或 replaceState() 方法的 state 参数的拷贝。
在浏览器可能触发 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 事件