Web路由

245 阅读6分钟

什么是路由

URL到函数的映射。

就是一个路径的解析,根据客户端提交的路径,将请求解析到相应的控制器上; 从 URL 找到处理这个 URL 的类和函数。

router和route的区别

route就是一条路由,它将一个URL路径和一个函数进行映射。

/users        ->  getAllUsers()
/users/count  ->  getUsersCount()

router可以理解为一个容器,或者说一种机制,管理了一组route。

服务器端路由

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

以 Express 为例,

app.get('/', (req, res) => {
  res.sendFile('index')
})

app.get('/users', (req, res) => {
  db.queryAllUsers()
    .then(data => res.send(data))
})

这里定义了两条路由:

  • 当访问 / 的时候,会返回 index 页面
  • 当访问 /users 的时候,会从数据库中取出所有用户数据并返回

不仅仅是URL

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

客户端路由

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

  • 基于Hash
  • 基于History API

(1) 基于Hash

在单页面(SPA)开发中,通过Hash可以实现前端路由,在url后缀存在#(锚点),用来做页面定位,即根据页面id将该元素所在的区域展示在可视区域,#后面内容的改变不会发送请求到服务器。

我们知道,URL中 # 及其后面的部分为 hash。例如:

const url = require('url')
var a = url.parse('http://example.com/#/foo/bar')
console.log(a.hash)
// => #/foo/bar

hash仅仅是客户端的一个状态,也就是说,当向服务器发请求的时候,hash部分并不会发过去。

通过监听 window 对象的 hashChange 事件,可以实现简单的路由。页面hash值可以通过 window.location.hash 属性获取,当url的hash值发生变化,会触发window对象的hashchange事件,通过监听 hashchange 事件,操作 window.location.hash 属性可以实现。例如:

window.onhashchange = function() {
  var hash = window.location.hash
  var path = hash.substring(1)

  switch (path) {
    case '/':
      showHome()
      break
    case '/users':
      showUsersList()
      break
    default:
      show404NotFound()
  }
}

(2) 基于History API

window.history (window是浏览器的全局对象,所以window.history和history相同)是浏览器提供的用来记录和操作浏览器页面历史栈的对象的接口,提供了常用的属性和方法:

history.back();     //回退
history.go(-1);     //等同于history.back();
history.forward();  //前进
history.go(1); //等同forward()
window.history.length; //历史栈页面的数量

H5对History进行了扩展,增加了两个重要的新的方法:

History.pushState()  //浏览器历史记录压栈,增加一条历史记录
History.replaceState() //浏览器历史记录最后一条数据更新,替换当前历史记录
通过HTML5 History API可以在不刷新页面的情况下,直接改变当前URL

我们可以通过监听 window 对象的 popstate 事件,来实现简单的路由:

window.onpopstate = function() {
  var path = window.location.pathname

  switch (path) {
    case '/':
      showHome()
      break
    case '/users':
      showUsersList()
      break
    default:
      show404NotFound()
  }
}

但是这种方法只能捕获前进或后退事件,无法捕获 pushState 和 replaceState,一种最简单的解决方法是替换 pushState 方法,例如:

var pushState = history.pushState
history.pushState = function() {
  pushState.apply(history, arguments)

  // emit a event or just run a callback
  emitEventOrRunCallback()
}

不过,最好的方法还是使用实现好的 history 库。

(3) 两种实现的比较

总的来说,基于Hash的路由,兼容性更好;基于History API的路由,更加直观和正式,用户体验好,响应快,不需要每次发送服务器请求,通过操作浏览器历史栈完成页面跳转。

但是,有一点很大的区别是,基于Hash的路由不需要对服务器做改动,基于History API的路由需要对服务器做一些改造。

假设服务器只有如下文件(script.js被index.html所引用):

/-
 |- index.html
 |- script.js

基于Hash的路径有:

http://example.com/
http://example.com/#/foobar

基于History API的路径有:

http://example.com/
http://example.com/foobar

当直接访问 / 的时候,两者的行为是一致的,都是返回了 index.html 文件。

当从 / 跳转到 /#/foobar 或者 /foobar 的时候,也都是正常的,因为此时已经加载了页面以及脚本文件,所以路由跳转正常。

当直接访问 /#/foobar 的时候,实际上向服务器发起的请求是 /,因此会首先加载页面及脚本文件,接下来脚本执行路由跳转,一切正常。

当直接访问 /foobar 的时候,实际上向服务器发起的请求也是 /foobar,然而服务器端只能匹配 / 而无法匹配 /foobar,因此会出现404错误。

因此如果使用了基于History API的路由,需要改造服务器端,使得访问 /foobar 的时候也能返回 index.html 文件,这样当浏览器加载了页面及脚本之后,就能进行路由跳转了。

一些区别

  • 前端路由和后端路由的区别:(网络请求方面)

    后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;

    前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;

    也就是,前端路由在跳转页面时不用向服务器请求资源,所以页面加载速度较快。

  • vue中router-link和传统a链接的区别:(DOM性能消耗,渲染方面)

    a标签跳转:从一张页面跳转到另一张页面。 通过a标签进行跳转,页面会被重新渲染,即相当于重新打开一个新的网页,体现为视觉上的“闪烁”(如果是本地的项目基本看不出来)

    router-link跳转:<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。通过router-link进行跳转不会跳转到新的页面,也不会重新渲染,它会选择路由所指的组件进行渲染,避免了重复渲染的“无用功”。

    也就是,对比<a>,router-link组件避免了不必要的重渲染,它只更新变化的部分从而减少DOM性能消耗。

Vue的创新之处在于,它利用虚拟DOM的概念和diff算法实现了对页面的"按需更新"。

Vue-router很好地继承了这一点,重渲染是我们不希望看到的,因为无论跳转到哪个页面,只需要渲染一次就够了。

反观<a>标签,每次跳转都得重渲染一次。"渲染"做了许多"无用功",而且消耗了大量弥足珍贵的DOM性能。

参考:

www.cnblogs.com/songyao666/…

zhuanlan.zhihu.com/p/24814675