路由实现原理

路由实现原理

路由的概念

  • 路由是根据不同的url地址展示不同的内容或者页面
  • 前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做

服务器端路由

对于服务器来说,当接收到客户端发来的HTTP请求,会根据请求的URL,来找到相应的映射函数,然后执行该函数,并将函数的返回值发送给客户端。对于最简单的静态资源服务器,可以认为,所有URL的映射函数就是一个文件读取操作。对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据的处理,等等。

以Express为例:

app.get('/', (req, res) => {
  // do something
  res.render('index');
})

app.get('/users', (req, res) => {
  // do something
  res.render('user');
})

这里就定义了两条路由

  • 当访问 / 的时候,会返回index页面
  • 当访问 /users 的时候,会返回user页面

在router匹配route的过程中,不仅会根据URL来匹配,还会根据请求的方法来看是否匹配。例如上面的例子,如果通过POST方法来访问 /users,就会找不到正确的路由

客户端路由

对于客户端(通常为浏览器)来说,路由的映射函数通常是进行一些DOM的显示和隐藏操作。这样,当访问不同的路径的时候,会显示不同的页面组件。客户端路由最常见的有以下两种实现方案:

  • 基于hash
  • 基于history

基于hash

URL中 # 及其后面的部分为hash。hash仅仅是客户端的一个状态,当向服务器发请求的时候,hash部分并不会发过去。
通过监听window对象的hashChange事件,可以实现简单的路。
例如,实现一个简单路由,点击触发URL的hash改变,相应地更新内容

<!DOCTYPE html>
<html>
<head>
  <title>Test</title>
  <meta charset="utf-8">
</head>
<body>
  <ul>
    <li><a href="#/">turn white</a></li>
    <li><a href="#/blue">turn blue</a></li>
    <li><a href="#/green">turn green</a></li>
  </ul>
</body>
</html>
class Router {
  constructor() {
    this.routes = {};
    this.currentUrl = '';
  }
  route(path, callback) {
    this.routes[path] = callback || function() {};
  }
  refresh() {
    this.currentUrl = location.hash.slice(1) || '/';
    this.routes[this.currentUrl]();
  }
  init() {
    window.addEventListener('load', this.refresh.bind(this), false);
    window.addEventListener('hashchange', this.refresh.bind(this), false);
  }
}
var router = new Router();
router.init();
// change Page anything
function changeBgColor(color) {
  document.body.style.backgroundColor = color;
}
router.route('/', function() {
  changeBgColor('white');
});
router.route('/blue', function() {
  changeBgColor('blue');
});
router.route('/green', function() {
  changeBgColor('green');
});

基于History API

通过 history.pushStatehistory.replaceState 可以在不刷新页面的情况下,直接改变当前URL。

history.pushState 方法接受三个参数,依次为:

  • state:一个与指定网址相关的状态对象,popState事件触发时,该对象会传入回调函数。
  • title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
  • url:新的网址,必须与当前页面处于同一个域。浏览器的地址栏将显示这个网址。
    假定当前网址为example.com/1.html,我们使用pushState方法在浏览记录里面(history对象)中添加一个新记录。
var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', '2.html');

添加上面这个新记录后,浏览器地址栏立刻显示example.com/2.html,但并不会跳转到2.html,甚至也不会检查2.html是否存在,它只是成为浏览历史中的最新记录。pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。如果设置了一个跨域网址就会报错。

history.replaceState方法的参数与前者一样,区别是它修改浏览历史中当前记录。

两种实现的比较

  • 基于Hash的路由,兼容性更好;基于History API的路由,更加直观和正式
  • 基于Hash的路由不需要对服务器做改动,基于History API的路由需要对服务器做一些改造