如何通过history.pushState获取有关历史更改的通知?

230
现在HTML5引入了history.pushState来改变浏览器历史记录,网站开始将其与Ajax结合使用,而不是更改URL的片段标识符。可悲的是,这意味着无法再通过onhashchange检测这些调用。
我的问题是:有没有可靠的方法(黑客?;))来检测网站何时使用history.pushState?规范没有说明任何被引发的事件(至少我找不到)。我尝试创建一个外观,并替换window.history为自己的JavaScript对象,但它根本没有任何效果。
进一步说明:我正在开发一个Firefox插件,需要检测这些更改并相应地采取行动。我知道几天前有一个类似的问题,问是否监听一些DOM事件会很有效,但我宁愿不依赖于它们,因为这些事件可能由许多不同的原因生成。
更新: 这里有一个jsfiddle(使用Firefox 4或Chrome 8),显示当调用pushState时不会触发onpopstate(或者我做错了什么?请随意改进!)。
更新2:
另一个(次要的)问题是在使用pushState时,window.location没有更新(但我认为我已经在SO上读到过这个问题)。

16个回答

1

既然您在询问Firefox插件,这是我使用的代码。使用unsafeWindow已经不再推荐,并且在修改后从客户端脚本调用pushState时会出现错误:

无法访问属性history.pushState的权限

相反,有一个名为exportFunction的API允许将函数注入到window.history中,如下所示:

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});

在最后一行,你应该将 unsafeWindow.history 改为 window.history。使用 unsafeWindow 对我来说无法正常工作。 - Lindsay-Needs-Sleep

0

基于@gblazex提供的解决方案,如果您想采用相同的方法,但使用箭头函数,请在您的JavaScript逻辑中参考以下示例:

private _currentPath:string;    
((history) => {
          //tracks "forward" navigation event
          var pushState = history.pushState;
          history.pushState =(state, key, path) => {
              this._notifyNewUrl(path);
              return pushState.apply(history,[state,key,path]); 
          };
        })(window.history);

//tracks "back" navigation event
window.addEventListener('popstate', (e)=> {
  this._onUrlChange();
});

接下来,实现另一个函数_notifyUrl(url),当当前页面的URL更新时触发任何必要的操作(即使页面根本没有被加载)

  private _notifyNewUrl (key:string = window.location.pathname): void {
    this._path=key;
    // trigger whatever you need to do on url changes
    console.debug(`current query: ${this._path}`);
  }

0

由于我只需要新的URL,所以我改编了@gblazex和@Alberto S.的代码来实现这一点:

(function(history){

  var pushState = history.pushState;
    history.pushState = function(state, key, path) {
    if (typeof history.onpushstate == "function") {
      history.onpushstate({state: state, path: path})
    }
    pushState.apply(history, arguments)
  }
  
  window.onpopstate = history.onpushstate = function(e) {
    console.log(e.path)
  }

})(window.history);

-1
我认为这个主题需要一个更现代的解决方案。
我确定 `nsIWebProgressListener` 早就存在了,我很惊讶没有人提到它。
从框架脚本(用于 e10s 兼容性):
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);

然后在onLoacationChange中监听

onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT

这将显然捕获所有的pushState。但是有一个警告评论说它“也会触发pushState”。因此,我们需要在这里进行更多的过滤,以确保它只是pushstate相关的内容。

基于:https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

和:resource://gre/modules/WebNavigationContent.js


这个回答与评论无关,除非我弄错了。WebProgressListeners是为FF扩展设计的吗?这个问题是关于HTML5历史记录的。 - Kloar
@Kloar,让我们删除这些评论。 - Noitidart

-1

你可以绑定到 window.onpopstate 事件吗?

https://developer.mozilla.org/en/DOM%3awindow.onpopstate

来自文档:

窗口上的popstate事件的事件处理程序。

每次活动历史记录条目更改时,窗口都会分派一个popstate事件。 如果正在激活的历史记录条目是通过调用history.pushState()创建的或受到调用history.replaceState()的影响, 则popstate事件的状态属性包含历史记录条目状态对象的副本。


10
我已经尝试过了,事件只有在用户返回历史记录时才会触发(或者调用任何history.go.back函数),但在pushState时不会触发。以下是我的测试尝试,也许我做错了什么:http://jsfiddle.net/fkling/vV9vd/ 似乎只与这种方式相关,如果历史记录是由pushState改变的,则当调用其他方法时,相应的状态对象将传递给事件处理程序。 - Felix Kling
2
啊,那种情况下我唯一能想到的就是注册一个超时来检查历史栈的长度,并在栈大小发生变化时触发事件。 - stef
1
好的,这是一个有趣的想法。唯一的问题是,超时时间必须经常触发,以便用户不会注意到任何(长时间的)延迟(我需要加载和显示新URL的数据)。我总是尽量避免使用超时和轮询,但到目前为止,这似乎是唯一的解决方案。我仍然会等待其他建议。但现在非常感谢! - Felix Kling
甚至可能只需在每次点击时检查历史记录的长度就足够了。 - Felix Kling
1
是的 - 这会起作用。我已经试过了 - 一个问题是replaceState调用不会抛出事件,因为堆栈的大小没有改变。 - stef

-7

根据标准规定:

请注意,仅调用history.pushState()或history.replaceState()不会触发popstate事件。只有通过浏览器操作(例如单击后退按钮(或在JavaScript中调用history.back()))才会触发popstate事件。

我们需要调用history.back()来触发WindowEventHandlers.onpopstate

因此,我们应该使用以下代码:

history.pushState(...)

做:

history.pushState(...)
history.pushState(...)
history.back()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接