前端路由原理

2019年03月09日Web前端

angular,vue,react都用过的吧,即使没用过也听过的吧,这些框架中都用到了路由这玩意,那么就来了解下他的原理吧。 前端路由的话,分为两类,一个是hash路由,另一个是HTML5的history路由。

一、hash路由

hash路由是我们经常用到的一种路由方式,因为他的兼容性相当的好,可以兼容到ie8。

hash路由的原理是监听url中#后部的变化,配合锚点的点击,触发hashchange事件,根据window.location.hash来做相应的处理。详细的可以查看:MDN

直接上代码,看下Hash原型,

var Hash = function() {
    this.hashFn = {};    // 存放路由与事件
    window.onhashchange = (e) => {
        var newUrl = e.newURL,
            newHash = newUrl.match(/#(w*)$/),
            env = this.hashFn[ newHash[1] ];
        
        env.fn.apply(env.ctx);
    };
}
Hash.prototype.bind = function(hash, fn, ctx = window) {
    this.hashFn[ hash ] = {
        fn,
        ctx
    }
}

hashFn用来存储对应hash值与其处理函数,随后通过监听hashchange事件,根据即将进入的hash值触发对应的处理函数。bind函数用于向hashFn中添加以hash为key值的处理函数和他的处理上下文,以供触发时使用。

var router = new Hash();
router.bind('a', function() {
    console.log('aaa');
});
router.bind('b', function() {
    console.log('bbb');
});
router.bind('c', function() {
    console.log('ccc');
});

利用new创建实例,并绑定好数据,此时点击页面上的链接就有反应了。demo可以查看:前端路由原理-hash

二、h5 history

使用h5 history api创建路由时,相比hash没有了#号,美观了很多。但是history的兼容性并不是太好,需要ie10+以上才支持。

h5 histroy api常用的有以下几个:

  • history.go(n),n为正数表示向前移动n个页面,负数亦然,n为0或为空时表示刷新当前页面。
  • history.forward(),相当于go(1)。
  • history.back(),相当于go(-1)。

h5中新增的:

  • history.pushState(data, title [, url]),向历史纪录中添加一条记录。
  • history.replaceState(data, title [, url]),更改当前的路由历史记录。
  • popstate事件,当历史记录发生改变时,就会触发该事件,即浏览器上的前进后退按钮事件和go()、forward()、back()事件,pushState和replaceState并不会触发该事件。

定义一个对象负责绑定事件和监听并触发事件,

var His = function() {
    this.eventFn = {};
    window.onpopstate = (e) => {
        this.eventTrigger(e.state)
    };
}
His.prototype.eventTrigger = function(state) {
    var name = state.name,
        env = this.eventFn[ name ];		
    env.fn.apply(env.ctx);
};
His.prototype.bind = function(name, fn, ctx = window) {
    this.eventFn[ name ] = {
        fn,
        ctx
    }
};

bind方法用于绑定要监听的事件和处理函数,eventTrigger用于触发bind绑定的函数。当浏览器获取到popstate事件时,就会根据触发对应的事件。 创建个实例,绑定下事件,

var his = new His();
his.bind('a', function() {
    console.log('a');
});
his.bind('b', function() {
    console.log('b');
});
his.bind('c', function() {
    console.log('c');
});

增加html,增加触发的条件,

<button onclick="a()">a</button>
<button onclick="b()">b</button>
<button onclick="c()">b</button>
<p id="box">^!^</p>
function a() {
    history.pushState({
        name: 'a'
    }, 'a', location.href.split('?')[0] + '?name=a');
    his.eventTrigger({name: 'a'});
}

function b() {
    history.pushState({
        name: 'b'
    }, 'b', location.href.split('?')[0] + '?name=b');
    his.eventTrigger({name: 'b'});
}

function c() {
    history.pushState({
        name: 'c'
    }, 'c', location.href.split('?')[0] + '?name=c');
    his.eventTrigger({name: 'c'});
}

为啥要在最后加个his.eventTrigger()函数呢?因为前面也说了,pushState并不会触发popstate事件。

这样history路由就已经完成了。demo:前端路由原理-history