vue-router 中常用的 hash 和 history 路由模式实现原理

183 阅读3分钟

前端实现路由的前提

改变url不刷新页面,前端负责监听url的改变,并匹配相应的页面进行渲染。 改变url不刷新页面有2种方式:

  1. has
  2. H5 history

接下来对这两种方式进行详细介绍:

1.hash模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。 location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':

https://www.word.com#search

hash 路由模式的实现主要是基于下面几个特性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮,或者调用history.go(),history.forward(),history.back(),控制hash的切换
  • 获取hash值:window.location.hash
  • 修改hash值:window.location.hash="#/index"
  • 监听hash值的改变:可以使用 hashchange 事件来监听 hash 值的变化
window.addEventListener('hashchange', function(){console.log('hash变了!')	}, false);
window.onhashchange =function onhashchange() {console.log('11111')}

2.history模式的实现原理

HTML5 引入了 history.pushState() 和 history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与window.onpopstate 配合使用。 这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,

用法如下所示:

window.history.pushState({data:'test1'}, 'title1', '/test1'); 
window.history.replaceState({data:'test2'}, 'title2', '/test2');

假如当前url为:mozilla.org/

  1. 使浏览器地址栏显示为 http://mozilla.org/test1,但并不会导致浏览器加载 http://mozilla.org/test1 ,甚至不会检查http://mozilla.org/test1 是否存在。
  2. pushState会在历史记录里添加一条新的记录
  3. replaceState会修改当前的历史记录
参数用途
状态对象关联到当前的url,当当前的url被激活,会将该状态对象的副本作为onPopState()回调函数的参数,也可以通过history.state获取到该对象的副本。
标题目前的浏览器都忽略了这个参数
URLurl,定义了新的历史 URL 记录,新的 URL 可以是与当前 URL 同源的任意 URL,也可以不传该参数,不传就为当前的url

怎么监听url的变化?

  1. 通过windon.popstate()

当点击浏览器上的前进和后退按钮时,或者调用history.go(),history.forward(),history.back()时,会触发onpopstate事件。通过onpopstate可以监听用户点击前进后退造成的url的改变。

    window.addEventListener('popstate', function(e) {
    //e.state,history.state都是当前url的状态对象的副本
    console.log('history.state',e.state,history.state)    
    console.log('onpopstate~');
    });

    window.onpopstate = function(){
      console.log('有人操作了浏览器的历史记录~')
    }

当使用pushState()和replaceSate()时,并不会触发onpopstate事件,所以需要对这两个方法进行改写 。 2. 改写history.pushState(),改写history.replaceSate()

const bindHistoryEvent = function(type) {
    const historyEvent = history[type];
    return function() {
        const newEvent = historyEvent.apply(this, arguments); //执行history函数
        const e = new Event(type);  //声明自定义事件
        e.arguments = arguments;
        window.dispatchEvent(e);  //抛出事件
        return newEvent;  //返回方法,用于重写history的方法
     };
    };
    history.pushState =  bindHistoryEvent('pushState');
    history.replaceState =  bindHistoryEvent('replaceState');

    window.addEventListener('replaceState', function(e) {
      console.log('replaceState~');
    });
    // history.state: 当前激活路径如果是使用pushState或者replaceState添加的记录,该值为传入的第一个参数
    window.addEventListener('pushState', function(e) {
      console.log('history.state',e.arguments,history.state)    
      console.log('pushState~');
    });

3.两种模式如何选择?

hashhistory
优势1.兼容性好 2.hash值不会发送到服务器不需要1.url中没有# 2.设置的url与当前url一致也会添加一条新的记录 3.可以添加状态对象和title
缺点1.url中有#1.兼容性不好 2.path的部分会被发送到服务器,需要服务端配置

参考:

30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度) - 掘金 (juejin.cn)

浅谈前端路由原理hash和history - 掘金 (juejin.cn)