JS实现监听路由变化
前言
同事最近正在做一个专项,叫Mapping数据采集系统。 他给我抛出了一个问题如何用原生js实现路由监听拦截并添加事件获取当前页面路径。(扬言你网上搜不到,还说给我一周都想不出来!!着实过分啦)
找到了这篇:https://juejin.cn/post/6844903790508933133#comment (啪啪被打脸hhhh~[坏笑ing])
功能诉求:监听拦截路由变化
开始前可以温习一下,前端路由的概念:你所理解都前端路由是什么?
我们知道前端路由实现方式有两种:
- 哈希模式
- 监听 onhashchange
- 历史模式
- history模式依赖都是原生事件popstate
- history.pushState() 或 history.replaceState() 不会触发popstate事件
既然要监听到所有类型页面的路由变化, 只用事件监听 hashchange 和popstate是无法满足需求的,所以需要对history对象的pushState和replaceState方法进行重写。
实现思路
- 完成一个订阅0发布模式
- 重写history.pushState,history.replaceState
- 添加消息通知(创建event-bus来实现通知)
- 触发事件订阅函数执行
订阅-发布模式Demo
class Dep{ //订阅池
constructor(name){
this.id = new Date() // 使用时间戳做订阅池的ID
this.subs = [] // 该事件下对象的集合
}
defined(){ // 添加订阅者
Dep.watch.add(this);
}
notify(){ // 通知订阅者有变化
this.subs.forEach((e,i)=>{
if(typeof e.update === 'function'){
try{
e.update.apply(e); // 触发订阅者更新函数
}catch(err){
console.warr(err);
}
}
})
}
}
Dep.watch = null;
class Watch{
constructor(name,fn){
this.name = name; // 订阅消息的名称
this.id = new Date(); // 使用时间戳做订阅者的ID
this.callBack = fn; // 订阅消息发送改变时 -> 订阅者执行的回调函数
}
add(dep){ // 将订阅者放入dep订阅池
dep.subs.push(this);
}
update(){ // 将订阅者更新方法
var cb = this.callBack; // 赋值为了不改变函数内调用的this
cb(this.name);
}
}
重写history方法,并添加window.addHistoryListener事件机制
下面我们只需要对history的方法进行重写,并添加event-bus即可,代码如下:
var addHistoryMethod = (function(){
var historyDep = new Dep();
return function(name){
if(name === 'historychange'){
return function(name,fn){
var event = new Watch(name,fn)
Dep.watch = event;
historyDep.defined();
Dep.watch = null; // 置空供下一个订阅者使用
}
}else if(name === 'pushState' || name === 'replaceState'){
var method = history[name];
return function(){
method.apply(history,arguments);
historyDep.notify();
}
}
}
}());
window.addHistoryListener = addHistoryMethod('historychange');
history.pushState = addHistoryMethod('pushState');
history.replaceState = addHistoryMethod('replaceState');
测试History事件监听
上面我们给window添加了一个addHistoryListener事件监听,类似于 addEventListener的方法,然后我们有做了history的pushState, replaceState的改写,接下来我们测试一下。
window.addEventListener('history',function(){
console.log('窗口的history改变了')
console.log('当前页面链接是:',window.location.href);
})
history.pushState({first:'first'},"page2","/first");
观察上面结果打印;我们发现window的 history改变,我们成功的添加了事件监听!
文章里也说了,目前这种实现方式还是有缺陷的, 就是少了事件的移除。后续测试针对多页面应用这种,此法方法有所局限, 还需要做进一步的兼容。