原文地址:http://syaning.com/2017/01/10...html
♪web
在Web开发过程当中,常常会遇到『路由』的概念。那么,到底什么是路由?简单来讲,路由就是URL到函数的映射。数据库
route就是一条路由,它将一个URL路径和一个函数进行映射,例如:浏览器
/users -> getAllUsers() /users/count -> getUsersCount()
这就是两条路由,当访问/users的时候,会执行getAllUsers()函数;当访问/users/count的时候,会执行getUsersCount()函数。服务器
而router能够理解为一个容器,或者说一种机制,它管理了一组route。简单来讲,route只是进行了URL和函数的映射,而在当接收到一个URL以后,去路由映射表中查找相应的函数,这个过程是由router来处理的。一句话归纳就是 “The router routes you to a route“。app
对于服务器来讲,当接收到客户端发来的HTTP请求,会根据请求的URL,来找到相应的映射函数,而后执行该函数,并将函数的返回值发送给客户端。对于最简单的静态资源服务器,能够认为,全部URL的映射函数就是一个文件读取操做。对于动态资源,映射函数多是一个数据库读取操做,也多是进行一些数据的处理,等等。函数
以Express为例,ui
app.get('/', (req, res) => { res.sendFile('index') }) app.get('/users', (req, res) => { db.queryAllUsers() .then(data => res.send(data)) })
这里定义了两条路由:url
当访问/的时候,会返回index页面
当访问/users的时候,会从数据库中取出全部用户数据并返回
不只仅是URLcode
在router匹配route的过程当中,不只会根据URL来匹配,还会根据请求的方法来看是否匹配。例如上面的例子,若是经过POST方法来访问/users,就会找不到正确的路由。
对于客户端(一般为浏览器)来讲,路由的映射函数一般是进行一些DOM的显示和隐藏操做。这样,当访问不一样的路径的时候,会显示不一样的页面组件。客户端路由最多见的有如下两种实现方案:
基于Hash
基于History API
(1) 基于Hash
咱们知道,URL中#及其后面的部分为hash。例如:
const url = require('url') var a = url.parse('http://example.com/a/b/#/foo/bar') console.log(a.hash) // => #/foo/bar
hash仅仅是客户端的一个状态,也就是说,当向服务器发请求的时候,hash部分并不会发过去。
经过监听window对象的hashChange事件,能够实现简单的路由。例如:
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
经过HTML5 History API能够在不刷新页面的状况下,直接改变当前URL。详细用法能够参考:
Manipulating the browser history Using the HTML5 History API
咱们能够经过监听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
当直接访问 http://example.com/
的时候,二者的行为是一致的,都是返回了index.html文件。
当从http://example.com/
跳转到http://example.com/#/foobar
或者http://example.com/foobar
的时候,也都是正常的,由于此时已经加载了页面以及脚本文件,因此路由跳转正常。
当直接访问http://example.com/#/foobar
的时候,实际上向服务器发起的请求是http://example.com/
,所以会首先加载页面及脚本文件,接下来脚本执行路由跳转,一切正常。
当直接访问http://example.com/foobar
的时候,实际上向服务器发起的请求也是http://example.com/foobar
,然而服务器端只能匹配/而没法匹配/foobar,所以会出现404错误。
所以若是使用了基于History API的路由,须要改造服务器端,使得访问/foobar的时候也能返回index.html文件,这样当浏览器加载了页面及脚本以后,就能进行路由跳转了。
上面提到的例子都是静态路由,也就是说,路径都是固定的。可是有时候咱们须要在路径中传入参数,例如获取某个用户的信息,咱们不可能为每一个用户建立一条路由,而是在经过捕获路径中的参数(例如用户id)来实现。
例如在Express中:
app.get('/user/:id', (req, res, next) => { // ... ... })
在Flask中:
@app.route('/user/<user_id>') def get_user_info(user_id): pass
在不少状况下,会遇到/foobar和/foobar/的状况,它们看起来很是相似,然而实际上有所区别,具体的行为也是视服务器设置而定。
在Flask的文档中,提到,末尾有斜线的路径,类比于文件系统的一个目录;末尾没有斜线的路径,类比于一个文件。所以访问/foobar的时候,可能会重定向到/foobar/,而反过来则不会。
若是使用的是Express,默认这二者是同样的,也能够经过app.set来设置strict routing,来区别对待这两种状况。