监听浏览器回退

一、前言

近来遇到一个需求需要监听浏览器回退事件,恰好目前项目中运用的路由为hash-router,因此对这方面做了一个总结。因此本文更多其实只能算对mdn相关文档的一个汇总笔记。

二、相关api

1.window.onpopstate

参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/onpopstate

每当活动的历史记录项发生变化时, popstate 事件都会被传递给window对象。如果当前活动的历史记录项是被 pushState 创建的,或者是由 replaceState 改变的,那么 popstate 事件的状态属性 state 会包含一个当前历史记录状态对象的拷贝。

(1) 概述:

根据mdn文档上的解释,我还想来画一下重点:

[1] window.onpopstate是popstate事件在window对象上的事件处理程序,每当处于激活状态的历史记录条目发生变化时,popstate事件就会在对应window对象上触发。

[2] 调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法)。

[3] 如果当前处于激活状态的历史记录条目是由history.pushState()方法创建,或者由history.replaceState()方法修改过的, 则popstate事件对象的state属性包含了这个历史记录条目的state对象的一个拷贝。

[4] 当网页加载时,各浏览器对popstate事件是否触发有不同的表现,Chrome 和 Safari会触发popstate事件, 而Firefox不会。

语法:

1
window.onpopstate = funcRef;

例子:

1
2
3
4
5
6
7
8
9
10
window.onpopstate = function(event) {
console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
//绑定事件处理函数.
history.pushState({page: 1}, "title 1", "?page=1"); //添加并激活一个历史记录条目 http://example.com/example.html?page=1,条目索引为1
history.pushState({page: 2}, "title 2", "?page=2"); //添加并激活一个历史记录条目 http://example.com/example.html?page=2,条目索引为2
history.replaceState({page: 3}, "title 3", "?page=3"); //修改当前激活的历史记录条目 http://ex..?page=2 变为 http://ex..?page=3,条目索引为3
history.back(); // 弹出 "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // 弹出 "location: http://example.com/example.html, state: null
history.go(2); // 弹出 "location: http://example.com/example.html?page=3, state: {"page":3}

2.history.pushState

参考:https://developer.mozilla.org/zh-CN/docs/Web/API/History_API

(1) 概念:添加历史记录中的条目

(2) pushState()使用

pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个URL. 让我们来解释下这三个参数详细内容:

1
pushState({ state1: 1, state2: 2 }, title, URL);
  • 状态对象:

    状态对象state是一个JavaScript对象,通过pushState () 创建新的历史记录条目。无论什么时候用户导航到新的状态,popstate事件就会被触发,且该事件的state属性包含该历史记录条目状态对象的副本。

    状态对象可以是能被序列化的任何东西。原因在于Firefox将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有640k的大小限制。如果你给 pushState() 方法传了一个序列化后大于640k的状态对象,该方法会抛出异常。如果你需要更大的空间,建议使用 sessionStorage 以及 localStorage.

    1
    这一句可以理解为,pushState()后不会立即跳转,此时会对该历史记录条目状态保存一个副本,无	论什么时候用户导航到新的状态,popstate事件就会被触发。
  • 标题

    Firefox 目前忽略这个参数,但未来可能会用到。传递一个空字符串在这里是安全的,而在将来这是不安全的。二选一的话,你可以为跳转的state传递一个短标题

  • URL

    该参数定义了新的历史URL记录。注意,调用 pushState() 后浏览器并不会立即加载这个URL,但可能会在稍后某些情况下加载这个URL,比如在用户重新打开浏览器时。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前URL。

(3) 例子

1
2
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");

使用 history.pushState() 可以改变referrer,它在用户发送 XMLHttpRequest 请求时在HTTP头部使用,改变state后创建的 XMLHttpRequest 对象的referrer都会被改变。因为referrer是标识创建 XMLHttpRequest 对象时 this 所代表的window对象中document的URL。

在某种意义上,调用 pushState() 与 设置 window.location = “#foo” 类似,二者都会在当前页面创建并激活新的历史记录。但 pushState() 具有如下几条优点:

  • 新的 URL 可以是与当前URL同源的任意URL 。而设置 window.location 仅当你只修改了哈希值时才保持同一个 document。

  • 如果需要,你可以不必改变URL。而设置 window.location = “#foo”;在当前哈希不是 #foo 的情况下, 仅仅是新建了一个新的历史记录项。

  • 你可以为新的历史记录项关联任意数据。而基于哈希值的方式,则必须将所有相关数据编码到一个短字符串里。

  • 假如 标题 在之后会被浏览器用到,那么这个数据是可以被使用的(哈希则不然)。

注意 pushState() 绝对不会触发 hashchange 事件,即使新的URL与旧的URL仅哈希不同也是如此。

3.history.replaceState

参考:https://developer.mozilla.org/zh-CN/docs/Web/API/History_API

(1) 概念:修改历史记录中的条目

history.replaceState() 的使用与 history.pushState() 非常相似,区别在于 replaceState() 是修改了当前的历史记录项而不是新建一个。 注意这并不会阻止其在全局浏览器历史记录中创建一个新的历史记录项。

replaceState() 的使用场景在于为了响应用户操作,你想要更新状态对象state或者当前历史记录的URL。

(2) replaceState()使用

1
2
var stateObj = { foo: "bar" };
history.replaceState(stateObj, "page 3", "bar2.html");

导致地址栏显示http://mozilla.org/bar2.html,,但是浏览器并不会去加载bar2.html 甚至都不需要检查 bar2.html 是否存在。

假设现在用户重新导向到了 http://www.microsoft.com,然后点击了回退按钮。这里,地址栏会显示http://mozilla.org/bar2.html。假如用户再次点击回退按钮,地址栏会显示http://mozilla.org/foo.html,完全跳过了bar.html。

4.HashChangeEvent

当URL中的片段标识符发生改变时触发(URL中紧跟’#’号的部分,包括‘#’号)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(function(window) {

// exit if the browser implements that event
if ( "onhashchange" in window.document.body ) { return; }

var location = window.location,
oldURL = location.href,
oldHash = location.hash;

// check the location hash on a 100ms interval
setInterval(function() {
var newURL = location.href,
newHash = location.hash;

// if the hash has changed and a handler has been bound...
if ( newHash != oldHash && typeof window.onhashchange === "function" ) {
// execute the handler
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});

oldURL = newURL;
oldHash = newHash;
}
}, 100);

})(window);

5.history.length

History.length是一个只读属性,返回当前session中的history个数,包含当前页面在内。举个例子,对于新开一个tab加载的页面当前属性返回值1。

1
2
3
length = history.length; // 返回当前session中的history个数

var result = window.history.length; // 返回当前session中的history个数

mdn文档中,对这块的翻译基本以上,实际在使用中有一点是非常需要注意的,就是history.length并不是整个当前会话的历史条目的当前状态(即currentLength),而实际是当前会话历史条目的存在记录的整体长度(即maxlength)

三、常用遇到的情况

1. 监听回退

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var detectBack = {

initialize: function() {
//监听hashchange事件
window.addEventListener('hashchange', function() {

//为当前导航页附加一个tag
this.history.replaceState('hasHash', '', '');

}, false);

window.addEventListener('popstate', function(e) {

if (e.state) {
//侦测是用户触发的后退操作, dosomething
//这里刷新当前url
this.location.reload();
}
}, false);
}
}

detectBack.initialize();

2. 阻止回退

1
2
3
4
5
6
7
8
9
10
function pushHash() {
window.history.pushState('home',null,'#/home'); // 跳转后压入一次历史,并表示home状态
window.history.pushState(null,null,'#/home'); // 跳转后压入一次历史,以便回退拦截
}

window.addEventListener('popstate',function (e) {
if(e.state === 'home') {
拦截回退
}
}
Share