现在HTML5引入了历史。pushState来改变浏览器的历史记录,网站开始与Ajax结合使用,而不是改变URL的片段标识符。
不幸的是,这意味着onhashchange再也不能检测到这些调用了。
我的问题是:有没有可靠的方法(hack?);))来检测网站何时使用history.pushState?规范没有说明引发的任何事件(至少我没有找到任何事件)。
我试图创造一个立面,并替换了窗户。我自己的JavaScript对象的历史,但它根本没有任何影响。
进一步解释:我正在开发一个Firefox插件,它需要检测这些变化并采取相应的行动。
我知道几天前有一个类似的问题,问监听一些DOM事件是否有效,但我宁愿不依赖于它,因为这些事件可以出于许多不同的原因生成。
更新:
这里是一个jsfiddle(使用Firefox 4或Chrome 8),显示onpopstate没有触发pushState被调用(或我做错了什么?请随意改进!)。
更新2:
另一个问题是那个窗口。使用pushState时,location不会更新(但我已经在这里读到这个,所以我认为)。
基于@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}`);
}
谢谢@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)
},
})
})
好吧,我看到了很多替换历史的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
我不认为这是一个好主意,即使你可以修改本机函数,你应该始终保持你的应用程序范围,所以一个好的方法是不使用全局pushState函数,而是使用你自己的一个:
function historyEventHandler(state){
// your stuff here
}
window.onpopstate = history.onpushstate = historyEventHandler
function pushHistory(...args){
history.pushState(...args)
historyEventHandler(...args)
}
<button onclick="pushHistory(...)">Go to happy place</button>
请注意,如果任何其他代码使用本机pushState函数,则不会获得事件触发器(但如果发生这种情况,则应该检查代码)