JS实现监听路由变化

前言

同事最近正在做一个专项,叫Mapping数据采集系统。 他给我抛出了一个问题如何用原生js实现路由监听拦截并添加事件获取当前页面路径。(扬言你网上搜不到,还说给我一周都想不出来!!着实过分啦)

找到了这篇: (啪啪被打脸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改变,我们成功的添加了事件监听!
文章里也说了,目前这种实现方式还是有缺陷的, 就是少了事件的移除。后续测试针对多页面应用这种,此法方法有所局限, 还需要做进一步的兼容。