如何通过history.pushState获得有关历史变化的通知?

Fel*_*ing 139 javascript firefox-addon browser-history pushstate

因此,现在HTML5引入history.pushState了更改浏览器历史记录,网站开始将其与Ajax结合使用,而不是更改URL的片段标识符.

可悲的是,这意味着不再能够检测到这些呼叫了onhashchange.

我的问题是:是否有可靠的方法(黑客?;))来检测网站何时使用history.pushState?规范没有说明引发的事件(至少我找不到任何东西).
我尝试创建一个外观并替换window.history为我自己的JavaScript对象,但它根本没有任何效果.

进一步说明:我正在开发一个需要检测这些更改并相应操作的Firefox附加组件.
我知道几天前有一个类似的问题,询问听一些DOM事件是否有效但我宁愿不依赖它,因为这些事件可能由于很多不同的原因而产生.

更新:

这是一个jsfiddle(使用Firefox 4或Chrome 8),显示调用onpopstate时不会触发pushState(或者我做错了什么?随意改进它!).

更新2:

另一个(侧面)问题是window.location在使用时没有更新pushState(但我已经在这里读到了这个,我认为).

gbl*_*zex 171

5.5.9.1事件定义

popstate导航到会话历史记录条目时,事件在某些情况下被解雇.

据此,当你使用popstate时没有理由被解雇pushState.但是像pushstate这样的事件会派上用场.因为它history是一个宿主对象,你应该小心它,但在这种情况下Firefox似乎很好.这段代码工作得很好:

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);
Run Code Online (Sandbox Code Playgroud)

你的jsfiddle 成为:

window.onpopstate = history.onpushstate = function(e) { ... }
Run Code Online (Sandbox Code Playgroud)

你可以window.history.replaceState用同样的方式进行猴子补丁.

注意:当然,您可以onpushstate简单地添加到全局对象,甚至可以通过它来处理更多事件add/removeListener

  • @ user280109 - 我会告诉你我是否知道.:)我认为在Opera atm中没有办法做到这一点. (2认同)

CBH*_*ing 11

终于找到了“正确”的方法来做到这一点!它需要为您的扩展程序添加权限并使用后台页面(不仅仅是内容脚本),但它确实有效。

您想要的事件是browser.webNavigation.onHistoryStateUpdated,当页面使用historyAPI 更改 URL时会触发该事件。它只针对您有权访问的站点触发,如果需要,您还可以使用 URL 过滤器进一步减少垃圾邮件。它需要webNavigation权限(当然还有相关域的主机权限)。

事件回调获取选项卡 ID、正在“导航”到的 URL 以及其他此类详细信息。如果您需要在事件触发时在该页面上的内容脚本中执行操作,请直接从后台页面注入相关脚本,或者让内容脚本port在加载时打开一个到后台页面,让后台页面保存由选项卡 ID 索引的集合中的该端口,并在事件触发时通过相关端口(从后台脚本到内容脚本)发送消息。

  • 不幸的是,该 API 仅适用于 Web 扩展和附加组件,而不是普通的 DOM API (24认同)
  • 正确,但这就是 OP 试图根据问题构建的内容。对于纯粹的网络内容,我认为您别无选择,只能包装函数和/或事件。 (2认同)

Mir*_*ili 7

感谢@KalanjDjordjeDjordje回答。我试图把他的想法变成一个完整的解决方案:

const onChangeState = (state, title, url, isReplace) => { 
    // define your listener here ...
}

// set onChangeState() listener:
['pushState', 'replaceState'].forEach((changeState) => {
    // store original values under underscored keys (`window.history._pushState()` and `window.history._replaceState()`):
    window.history['_' + changeState] = window.history[changeState]
    
    window.history[changeState] = new Proxy(window.history[changeState], {
        apply (target, thisArg, argList) {
            const [state, title, url] = argList
            onChangeState(state, title, url, changeState === 'replaceState')
            
            return target.apply(thisArg, argList)
        },
    })
})
Run Code Online (Sandbox Code Playgroud)


小智 6

除了其他答案。我们可以使用 History 接口,而不是存储原始函数。

history.pushState = function()
{
    // ...

    History.prototype.pushState.apply(history, arguments);
}
Run Code Online (Sandbox Code Playgroud)


Kal*_*dje 6

我用简单的代理来做到这一点。这是原型的替代品

window.history.pushState = new Proxy(window.history.pushState, {
  apply: (target: any, thisArg: any, argArray?: any) => {
    // trigger here what you need
    return target.apply(thisArg, argArray);
  },
});
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说非常有效。(我不需要支持 IE。)但是,请注意:这是 TypeScript。要转换为纯 JavaScript,您需要删除每个参数上的 `:any`。我还要指出,`:any` 在 TypeScript 中也是多余的。 (4认同)
  • 从那以后我发现了一点点变化。如所写,“触发”代码在页面历史记录更改之前触发。这使得测试新 URL 变得困难。此后,我更改了 apply 内的代码: `let output = target.apply(thisArg, argArray); /* 触发代码 */ 返回输出;` (4认同)

Rud*_*die 5

我曾经使用过这个:

var _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('replaceState', function(e) {
    console.warn('THEY DID IT AGAIN!');
});
Run Code Online (Sandbox Code Playgroud)

这几乎和galambalazs做的一样。

不过这通常是矫枉过正。它可能不适用于所有浏览器。(我只关心我的浏览器版本。)

(它留下了一个 var _wr,所以你可能想要包装它或其他东西。我不在乎那个。)