浏览器地址变换,popstate、pushstate以及replacestate事件
发布于 作者:苏南大叔 来源:程序如此灵动~ 我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...
需求是这样的:页面监控浏览器里地址栏里的url
变化情况。那么问题来了,页面的url
变化,(虚假),页面加载的源码就会发生变化,那么相关的程序就会发生重载,无法持续进行地址的监控。所以,这似乎存在着一个悖论。那么,真实的情况是怎么样的呢?
苏南大叔的“程序如此灵动”博客,记录苏南大叔和计算机代码的故事。本文测试环境:chrome@130.0.6723.92
。
需求悖论
这个事情从需求到实现,都存在着悖论。比如:
虚假的悖论
事实上,地址栏显示的网址发生变化,页面【不一定】会重新到服务器请求源码的。为了持续监控地址变化,需要保证页面不被刷新。举例来说:
- 普通的页面,变化
#
后面的hash
的时候,页面也是不刷新的。 react
和vue
们,它们就是只变化地址栏,而不重新请求服务器的。
因此,对于页面仅变化hash
,或者react
和vue
的情况来说,页面并不刷新,持续监控地址栏变化是可行的。
真实的悖论
因为监控的手段就是监听相关的地址变化事件(仅变化#hash
,也属于其中)。相关事件一共有四个。分别是:
pushstate
,产生新的地址。popstate
,在老地址之间变换。例如:历史前进或者历史后退动作。replacestate
,替换当前的地址。hashchange
,变换#hash
时触发,但同时会触发pushstate
或popstate
。
出乎意料的是,只有popstate
事件是可以被监控到的。另外的pushstate
和replacestate
事件,无法被监控到。参考文章:
也就是说,正常情况下,只有点击左上角的前进后退按钮,或者代码执行history.back()
等事件的时候,才能被监控到popstate
事件。
改写函数打补丁
为了解决上述问题,这里利用函数改写的方法,在.pushstate()
和.replacestate()
函数中,hook
了对应的事件。从而在外部能够对这两个事件做出对应的反应,这算打个系统补丁。
有关技术实现的细节,可以参考文章:
- https://newsn.net/say/js-hook.html
- https://newsn.net/say/func-args.html
- https://newsn.net/say/js-apply.html
- https://newsn.net/say/js-custom-event.html
pushstate
(function (history) {
var pushState = history.pushState;
history.pushState = function (state, title, url) {
var result = pushState.apply(history, arguments);
var event = new CustomEvent("pushstate", {
detail: { state: state, title: title, url: url },
});
window.dispatchEvent(event);
return result;
};
})(window.history);
replacestate
(function (history) {
var replaceState = history.replaceState;
history.replaceState = function (state, title, url) {
var result = replaceState.apply(history, arguments);
var event = new CustomEvent("replacestate", {
detail: { state: state, title: title, url: url },
});
window.dispatchEvent(event);
return result;
};
})(window.history);
完整测试代码
值得一提的是:这段代码必须放在nginx
之类的服务器下,才能正常使用。双击是不行的,会得到错误提示信息:
Uncaught SecurityError: Failed to execute 'pushState' on 'History': A history state object with URL 'file:///D:/t/1.html' cannot be created in a document with origin 'null' and URL 'file:///D:/t/index.html#c'
完整的测试代码如下:
<style>
a,button{
border: 1px solid #000;
text-align: center;
font-size:14px;
height:25px;
background:#f0f0f0;
margin-bottom: 6px;
text-decoration: none;
color:#000;
display: block;
float:left;
margin-right: 2px;
}
</style>
<script>
(function (history) {
var pushState = history.pushState;
var replaceState = history.replaceState;
history.pushState = function (state, title, url) {
var result = pushState.apply(history, arguments);
var event = new CustomEvent("pushstate", {
detail: { state: state, title: title, url: url },
});
window.dispatchEvent(event);
return result;
};
history.replaceState = function (state, title, url) {
var result = replaceState.apply(history, arguments);
var event = new CustomEvent("replacestate", {
detail: { state: state, title: title, url: url },
});
window.dispatchEvent(event);
return result;
};
})(window.history);
function fakego(path) {
window.history.pushState(null, null, path);
}
function fakegogo(path) {
window.history.replaceState(
{ foo: "bar" + path },
"title" + path,
path
);
}
</script>
<a href="#a">改变Hash:a</a>
<a href="#b">改变Hash:b</a>
<button onclick="javascript:fakego('1/1.html')">pushState1</button>
<button onclick="javascript:fakego('2/2.html')">pushState2</button>
<button onclick="javascript:fakegogo('3/3.html')">replaceState3</button>
<button onclick="javascript:history.back()">后退</button>
<button onclick="javascript:history.forward()">前进</button>
<script>
window.addEventListener("pushstate", function (event) {
console.log("pushstate:" + document.location);
console.log("referer:" + document.referrer);
});
window.addEventListener("replacestate", function (event) {
console.log("replacestate:" + document.location);
console.log("referer:" + document.referrer);
});
window.addEventListener("popstate", function (event) {
console.log("popstate:" + document.location);
console.log("referer:" + document.referrer);
});
window.addEventListener("hashchange", function () {
console.log("hashchange:" + document.location);
console.log("referer:" + document.referrer);
});
</script>
这段代码运行后,其实还可以得出结论:
- 就是这些
pushstate
/popstate
/replacestate
/hashchange
,都不会影响来源页的变化【待议】,也就是说当前的页面确实还没有发生变化,只是变标没变本。 hashchange
可能伴随popstate
或者pushstate
。
事件监听
打完补丁后,核心监控代码就这样:
window.addEventListener("popstate", function (event) {
console.log("popstate:" + document.location);
});
window.addEventListener("pushstate", function (event) {
console.log("pushstate:" + document.location);
});
window.addEventListener("replacestate", function (event) {
console.log("replacestate:" + document.location);
});
结束语
更多苏南大叔的经验文章,请参考:
如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。