阅读 101

前端路由的两种实现原理

说明

前端路由 是现代SPA应用必备的功能,每个现代前端框架都有对应的实现,例如vue-routerreact-router

早期的路由都是后端实现的,直接根据 url 来reload页面,页面变得越来越复杂服务器端压力变大,随着 ajax 的出现,页面实现非 reload 就能刷新数据,也给前端路由的出现奠定了基础。我们可以通过记录 url 来记录 ajax 的变化,从而实现前端路由。

本文主要讲两种主流方式实现前端路由。

  • hash (#)
  • History (pushState、replaceState)

1.Hush

hash路由一个明显的标志是带有#,我们主要是通过监听url中的hash变化来进行路由跳转。

hash的优势就是兼容性更好,在老版IE中都有运行,问题在于url中一直存在#不够美观,而且hash路由更像是Hack而非标准,相信随着发展更加标准化的History API会逐步蚕食掉hash路由的市场。

下面是简单理解hash跳转页面原理。(下篇文章将结合historyreact-router源码介绍如何实现)

1.1初始化class

我们用Class关键字初始化一个路由.

1.2 实现路由hash储存与执行

在初始化完毕后我们需要思考两个问题:

  1. 将路由的hash以及对应的callback函数储存
  2. 监听路由hash变化后,执行对应的callback函数

1.3 监听对应事件

那么我们只需要在实例化Class的时候监听上面的事件即可.

1.4增加回退功能

我们在需要创建一个数组history来储存过往的hash路由例如/blue,并且创建一个指针currentIndex来随着后退和前进功能移动来指向不同的hash路由。

我们看起来实现的不错,可是出现了Bug,在后退的时候我们往往需要点击两下。

问题在于,我们每次在后退都会执行相应的callback,这会触发refresh()执行,因此每次我们后退,history中都会被push新的路由hash,currentIndex也会向前移动,这显然不是我们想要的。

1.5完整实现hash Router

class Routers {
  constructor() {
    // 储存hash与callback键值对
    this.routes = {};
    // 当前hash
    this.currentUrl = '';
    // 记录出现过的hash
    this.history = [];
    // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
    this.currentIndex = this.history.length - 1;
    this.refresh = this.refresh.bind(this);
    this.backOff = this.backOff.bind(this);
    // 默认不是后退操作
    this.isBack = false;
    window.addEventListener('load', this.refresh, false);
    window.addEventListener('hashchange', this.refresh, false);
  }

  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

  refresh() {
    this.currentUrl = location.hash.slice(1) || '/';
    if (!this.isBack) {
      // 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
      // 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
      // 避免再次造成指针的不匹配,我们直接截取指针之前的数组
      // 此操作同时与浏览器自带后退功能的行为保持一致
      if (this.currentIndex < this.history.length - 1)
        this.history = this.history.slice(0, this.currentIndex + 1);
      this.history.push(this.currentUrl);
      this.currentIndex++;
    }
    this.routes[this.currentUrl]();
    console.log('指针:', this.currentIndex, 'history:', this.history);
    this.isBack = false;
  }
  // 后退功能
  backOff() {
    // 后退操作设置为true
    this.isBack = true;
    this.currentIndex <= 0
      ? (this.currentIndex = 0)
      : (this.currentIndex = this.currentIndex - 1);
    location.hash = `#${this.history[this.currentIndex]}`;
    this.routes[this.history[this.currentIndex]]();
  }
}

复制代码

2.History

2.1 常用API

下面是常见的几个api,请去MDN-History查看。

  • window.history.back(); // 后退
  • window.history.forward(); // 前进
  • window.history.go(-3); // 后退三个页面

Html5新增了history.pushState用于在浏览历史中添加历史记录,但是并不触发跳转,此方法接受三个参数,依次为:

state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

history.replaceState方法的参数与pushState方法一模一样,区别是它修改浏览历史中当前纪录,而非添加记录,同样不触发跳转。

popstate事件,每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。 需要注意的是,仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用backforwardgo方法时才会触发。

另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。

以上API介绍选自history对象,可以点击查看完整版,我们不想占用过多篇幅来介绍API。

2.2 新标准下路由的实现

上一节我们介绍了新标准的History API,相比于我们在Hash 路由实现的那些操作,很显然新标准让我们的实现更加方便和可读。

所以一个mini路由实现起来其实很简单

总结

这篇文章主要是简单了解路由的实现原理,路由库最大的作用就是同步 URL 与其对应的回调函数。对于基于 history的路由,它通过 history.pushState 来修改 URL,通过 window.addEventListener('popstate', callback) 来监听前进/后退事件;对于 hash 路由,通过操作 window.location 的字符串来更改 hash,通过 window.addEventListener('hashchange', callback) 来监听 URL 的变化。

摘抄自你了解前端路由吗?,熟悉原理,下一步探索源码,再做进一步分析。

关注下面的标签,发现更多相似文章
评论