window.history

3,453 阅读5分钟

定义

window.history 提供了对浏览器会话历史的访问,它暴露了很多有用的方法和属性,允许你在用户浏览历史中向前和向后跳转,同时(从 HTML5 开始)提供了对 history 栈中内容的操作。

history 属性

属性说明
lengthhistory 栈中页面的数量。
statehistory 栈中当前页面的状态值。可在 popstate 事件的 callback 中获取(event.state),也可通过 history.state 获取。

history 方法

方法说明调用
forward向前跳转,和用户点击浏览器前进按钮的效果相同,无参数。history.forward() // 前进一个页面
back向后跳转,和用户点击浏览器回退按钮的效果相同,无参数。history.back() // 后退一个页面
go载入会话历史中的某一特定页面, 通过与当前页面的相对位置来标志 (当前页面的相对位置为0)。history.go(-1) // 后退一个页面,等同于调用了 back()
history.go(1) // 前进一个页面,等同于调用了 forward()
pushState添加历史记录。通常与 window.onpopstate 配合使用。
replaceState修改当前历史记录。通常与 window.onpopstate 配合使用。

pushState

history.pushState(state, title, url) // 往 history 栈栈顶压入一条新的历史记录,并改变当前指针至栈顶。

state: 状态对象 state 是一个 JavaScript 对象,必填。用户导航到新的状态,popstate 事件就会被触发。

title: Firefox 目前忽略这个参数,但未来可能会用到,可以传一个空字符串,必填。

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

replaceState

参数与 pushState 相同,含义也相同。

区别在于 replaceState 是修改了当前的历史记录而不是新建一个。注意这并不会阻止其在全局浏览器历史记录中创建一个新的历史记录项。

popstate

当历史记录发生变化时,将触发 popstate 事件。如果被激活的历史记录是通过调用 history.pushState() or history.replaceState() 创建的,popstate 事件的 state 属性包含历史记录的状态对象的副本。

直接调用 history.pushState() or history.replaceState() 不会触发 popstate 事件。只有在作出浏览器动作时,才会触发该事件,如用户去点击浏览器的前进/回退按钮,或者在 JavaScript 代码中调用 history.forward() / history.back()

不同的浏览器在加载页面时处理 popstate 事件的形式存在差异。页面加载时 Chrome 和 Safari 通常会触发popstate 事件,但 Firefox 则不会。

示例

打开 example,在控制台输入如下代码:

window.addEventListener('popstate', (event) => {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
});
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
history.back(); // Logs "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // Logs "location: http://example.com/example.html, state: null"
history.go(2);  // Logs "location: http://example.com/example.html?page=3, state: {"page":3}"

注意,虽然原始的历史项( http://example.com/example.html所对应的)没有关联的状态对象,但稍后调用 history.back() 激活该历史项时,仍会触发 popstate 事件。

应用

利用 pushState 等方法解决使用 Ajax 导致页面前进 & 后退的问题。

背景

  • 传统的 Web 开发模式

    • 页面中用户的每一次操作都将触发一次 HTTP 请求,服务器进行相应的处理后,返回一个 HTTP 页面给客户端。
  • Ajax 开发模式

    • Ajax 是一种无需重新加载网页就能更新部分页面数据的技术。
    • 在 Ajax 应用中,页面中用户的操作将通过 Ajax 引擎与服务器通信,然后将返回结果提交给客户端页面的 Ajax 引擎,再由 Ajax 引擎来决定将这些数据插入到页面的指定位置。
  • 优点 & 问题

    • 在 Ajax 开发模型中可以通过 JavaScript 实现不刷新整个页面的情况下,对部分数据进行更新,从而降低网络流量,给用户带来更好的交互体验。
    • 但是,Ajax 没法维持页面状态,也就是前进或后退之后,之前通过 Ajax 更新的内容就没了。

示例

假如要实现这么一个页面,布局如下,切换左侧的导航可变区发生相应变化。 layout

整个页面只有可变区的内容是经常变化的,如果采用传统的 Web 开发模式,那么每次请求的时候,标题区 & 导航区这类不变的内容也要重新渲染,资源浪费。所以,可变区通过 Ajax 局部刷新才是更优的策略。由于 Ajax 没法维持页面状态,我们还要解决前进后退问题。

所以,需要实现两个功能:

  • 通过 Ajax 局部刷新。
  • 前进后退的时候保留 Ajax 的更新。

实现方法:

  • 页面首次载入时,如果没有查询地址、或者查询地址不匹配,使用 history.replaceState 更改当前的浏览器历史,然后触发 Ajax 操作。
  • 每次手动点击左侧的菜单,我将 Ajax 地址的查询内容(?后面的)附在demo HTML页面地址后面,使用 history.pushState 塞到浏览器历史中。
  • 浏览器的前进与后退,会触发 window.onpopstate 事件,通过绑定 popstate 事件,就可以根据当前 URL 地址中的查询内容让对应的菜单执行 Ajax 载入,实现 Ajax 的前进与后退效果。

代码如下:

var eleMenus = $("#choMenu a").bind("click", function(event) {
    var query = this.href.split("?")[1];
    if (history.pushState && query && !$(this).hasClass(clMenuOn)) { // 支持 pushState & 有查询字符 & 不是点击当前选中的导航
        /*
            发送 Ajax 请求
        */
        
        // history 处理
        var title = "上海3月开盘项目汇总-" + $(this).text().replace(/\d+$/, "");
        document.title = title;
        if (event && /\d/.test(event.button)) { // 点击导航触发的 click 事件
            history.pushState({ title: title }, title, location.href.split("?")[0] + "?" + query);
        }
    }
    return false;
});

var fnHashTrigger = function (target) {
    var query = location.href.split("?")[1], eleTarget = target || null;
    if (typeof query == "undefined") { // 如果没有查询字符,则使用第一个导航元素的查询字符
      eleTarget = eleMenus.get(0);
      history.replaceState(null, document.title, location.href.split("#")[0] + "?" + eleTarget.href.split("?")[1]) + location.hash; // https://www.zhangxinxu.com/study/201306/ajax-page-html5-history-api.html?area=pudong
      fnHashTrigger(eleTarget);
    } else {
        eleMenus.each(function() {
            if (eleTarget === null && this.href.split("?")[1] === query) {
                eleTarget = this;
            }
        });
        
        if (!eleTarget) {
            // 如果查询字符没有对应的导航菜单,使用当前导航
            history.replaceState(null, document.title, location.href.split("?")[0]);    
            fnHashTrigger();
        } else {
            $(eleTarget).trigger("click"); // 手动调用触发
        }        
    }    
};
  
if (history.pushState) {
    window.addEventListener("popstate", function() {
        fnHashTrigger();                             
    });
    
    // 首次加载
    fnHashTrigger();
}

流程图:

参考

Manipulating the browser history

ajax与HTML5 history pushState/replaceState实例