现在HTML5引入了历史。pushState来改变浏览器的历史记录,网站开始与Ajax结合使用,而不是改变URL的片段标识符。

不幸的是,这意味着onhashchange再也不能检测到这些调用了。

我的问题是:有没有可靠的方法(hack?);))来检测网站何时使用history.pushState?规范没有说明引发的任何事件(至少我没有找到任何事件)。 我试图创造一个立面,并替换了窗户。我自己的JavaScript对象的历史,但它根本没有任何影响。

进一步解释:我正在开发一个Firefox插件,它需要检测这些变化并采取相应的行动。 我知道几天前有一个类似的问题,问监听一些DOM事件是否有效,但我宁愿不依赖于它,因为这些事件可以出于许多不同的原因生成。

更新:

这里是一个jsfiddle(使用Firefox 4或Chrome 8),显示onpopstate没有触发pushState被调用(或我做错了什么?请随意改进!)。

更新2:

另一个问题是那个窗口。使用pushState时,location不会更新(但我已经在这里读到这个,所以我认为)。


当前回答

好吧,我看到了很多替换历史的pushState属性的例子,但我不确定这是一个好主意,我更喜欢创建一个基于类似历史API的服务事件,这样你不仅可以控制推送状态,还可以控制替换状态,它为许多其他不依赖于全局历史API的实现打开了大门。请看下面的例子:

function HistoryAPI(history) {
    EventEmitter.call(this);
    this.history = history;
}

HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);

const prototype = {
    pushState: function(state, title, pathname){
        this.emit('pushstate', state, title, pathname);
        this.history.pushState(state, title, pathname);
    },

    replaceState: function(state, title, pathname){
        this.emit('replacestate', state, title, pathname);
        this.history.replaceState(state, title, pathname);
    }
};

Object.keys(prototype).forEach(key => {
    HistoryAPI.prototype = prototype[key];
});

如果你需要EventEmitter定义,上面的代码是基于NodeJS事件发射器:https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js。跑龙套。继承实现可以在这里找到:https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970

其他回答

galambalazs的答案monkey补丁window.history. pushstate和window.history。但由于某种原因,它不再为我工作了。这里有一个替代方案,不太好,因为它使用轮询:

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();

因为我只是想要新的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);

好吧,我看到了很多替换历史的pushState属性的例子,但我不确定这是一个好主意,我更喜欢创建一个基于类似历史API的服务事件,这样你不仅可以控制推送状态,还可以控制替换状态,它为许多其他不依赖于全局历史API的实现打开了大门。请看下面的例子:

function HistoryAPI(history) {
    EventEmitter.call(this);
    this.history = history;
}

HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);

const prototype = {
    pushState: function(state, title, pathname){
        this.emit('pushstate', state, title, pathname);
        this.history.pushState(state, title, pathname);
    },

    replaceState: function(state, title, pathname){
        this.emit('replacestate', state, title, pathname);
        this.history.replaceState(state, title, pathname);
    }
};

Object.keys(prototype).forEach(key => {
    HistoryAPI.prototype = prototype[key];
});

如果你需要EventEmitter定义,上面的代码是基于NodeJS事件发射器:https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js。跑龙套。继承实现可以在这里找到:https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970

我认为这个话题需要一个更现代的解决方案。

我确信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”。所以我们需要做更多的过滤来确保它只是推送状态的东西。

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

和:资源:/ / gre /模块/ WebNavigationContent.js

标准状态如下:

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

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

所以不是:

history.pushState(...)

do:

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