1. 什么是路由
在Web開發(fā)過程中炉爆,經(jīng)常會(huì)遇到『路由』的概念蔬浙。那么,到底什么是路由誊爹?簡單來說蹬刷,路由就是URL到函數(shù)的映射。
2. router和route的區(qū)別
route就是一條路由频丘,它將一個(gè)URL路徑和一個(gè)函數(shù)進(jìn)行映射办成,例如:
```
/users? ? ? ? ->? getAllUsers()
/users/count? ->? getUsersCount()
```
這就是兩條路由,當(dāng)訪問/users的時(shí)候搂漠,會(huì)執(zhí)行g(shù)etAllUsers()函數(shù)迂卢;當(dāng)訪問/users/count的時(shí)候,會(huì)執(zhí)行g(shù)etUsersCount()函數(shù)桐汤。
而router可以理解為一個(gè)容器而克,或者說一種機(jī)制,它管理了一組route怔毛。簡單來說员萍,route只是進(jìn)行了URL和函數(shù)的映射,而在當(dāng)接收到一個(gè)URL之后拣度,去路由映射表中查找相應(yīng)的函數(shù)碎绎,這個(gè)過程是由router來處理的。一句話概括就是 “The router routes you to a route“抗果。
3. 服務(wù)器端路由
對(duì)于服務(wù)器來說筋帖,當(dāng)接收到客戶端發(fā)來的HTTP請(qǐng)求,會(huì)根據(jù)請(qǐng)求的URL冤馏,來找到相應(yīng)的映射函數(shù)日麸,然后執(zhí)行該函數(shù),并將函數(shù)的返回值發(fā)送給客戶端宿接。對(duì)于最簡單的靜態(tài)資源服務(wù)器赘淮,可以認(rèn)為,所有URL的映射函數(shù)就是一個(gè)文件讀取操作睦霎。對(duì)于動(dòng)態(tài)資源,映射函數(shù)可能是一個(gè)數(shù)據(jù)庫讀取操作走诞,也可能是進(jìn)行一些數(shù)據(jù)的處理副女,等等。
以Express為例蚣旱,
app.get('/',(req,res)=>{res.sendFile('index')})app.get('/users',(req,res)=>{db.queryAllUsers().then(data=>res.send(data))})
這里定義了兩條路由:
當(dāng)訪問/的時(shí)候碑幅,會(huì)返回index頁面
當(dāng)訪問/users的時(shí)候戴陡,會(huì)從數(shù)據(jù)庫中取出所有用戶數(shù)據(jù)并返回
不僅僅是URL
在router匹配route的過程中,不僅會(huì)根據(jù)URL來匹配沟涨,還會(huì)根據(jù)請(qǐng)求的方法來看是否匹配恤批。例如上面的例子,如果通過POST方法來訪問/users裹赴,就會(huì)找不到正確的路由喜庞。
4. 客戶端路由
對(duì)于客戶端(通常為瀏覽器)來說,路由的映射函數(shù)通常是進(jìn)行一些DOM的顯示和隱藏操作棋返。這樣延都,當(dāng)訪問不同的路徑的時(shí)候,會(huì)顯示不同的頁面組件睛竣∥浚客戶端路由最常見的有以下兩種實(shí)現(xiàn)方案:
基于Hash
基于History API
(1) 基于Hash
我們知道,URL中#及其后面的部分為hash射沟。例如:
consturl=require('url')vara=url.parse('http://example.com/a/b/#/foo/bar')console.log(a.hash)// => #/foo/bar
hash僅僅是客戶端的一個(gè)狀態(tài)殊者,也就是說,當(dāng)向服務(wù)器發(fā)請(qǐng)求的時(shí)候验夯,hash部分并不會(huì)發(fā)過去猖吴。
通過監(jiān)聽window對(duì)象的hashChange事件,可以實(shí)現(xiàn)簡單的路由簿姨。例如:
window.onhashchange=function(){varhash=window.location.hashvarpath=hash.substring(1)switch(path){case'/':showHome()breakcase'/users':showUsersList()breakdefault:show404NotFound()}}
(2) 基于History API
通過HTML5 History API可以在不刷新頁面的情況下距误,直接改變當(dāng)前URL。詳細(xì)用法可以參考:
Manipulating the browser history
我們可以通過監(jiān)聽window對(duì)象的popstate事件扁位,來實(shí)現(xiàn)簡單的路由:
window.onpopstate=function(){varpath=window.location.pathnameswitch(path){case'/':showHome()breakcase'/users':showUsersList()breakdefault:show404NotFound()}}
但是這種方法只能捕獲前進(jìn)或后退事件准潭,無法捕獲pushState和replaceState,一種最簡單的解決方法是替換pushState方法域仇,例如:
varpushState=history.pushStatehistory.pushState=function(){pushState.apply(history,arguments)// emit a event or just run a callbackemitEventOrRunCallback()}
不過刑然,最好的方法還是使用實(shí)現(xiàn)好的history庫。
(3) 兩種實(shí)現(xiàn)的比較
總的來說暇务,基于Hash的路由泼掠,兼容性更好;基于History API的路由垦细,更加直觀和正式择镇。
但是,有一點(diǎn)很大的區(qū)別是括改,基于Hash的路由不需要對(duì)服務(wù)器做改動(dòng)腻豌,基于History API的路由需要對(duì)服務(wù)器做一些改造。下面來詳細(xì)分析。
假設(shè)服務(wù)器只有如下文件(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
當(dāng)直接訪問http://example.com/的時(shí)候吝梅,兩者的行為是一致的虱疏,都是返回了index.html文件。
當(dāng)從http://example.com/跳轉(zhuǎn)到http://example.com/#/foobar或者h(yuǎn)ttp://example.com/foobar的時(shí)候苏携,也都是正常的做瞪,因?yàn)榇藭r(shí)已經(jīng)加載了頁面以及腳本文件,所以路由跳轉(zhuǎn)正常右冻。
當(dāng)直接訪問http://example.com/#/foobar的時(shí)候装蓬,實(shí)際上向服務(wù)器發(fā)起的請(qǐng)求是http://example.com/,因此會(huì)首先加載頁面及腳本文件国旷,接下來腳本執(zhí)行路由跳轉(zhuǎn)矛物,一切正常。
當(dāng)直接訪問http://example.com/foobar的時(shí)候跪但,實(shí)際上向服務(wù)器發(fā)起的請(qǐng)求也是http://example.com/foobar履羞,然而服務(wù)器端只能匹配/而無法匹配/foobar,因此會(huì)出現(xiàn)404錯(cuò)誤屡久。
因此如果使用了基于History API的路由忆首,需要改造服務(wù)器端,使得訪問/foobar的時(shí)候也能返回index.html文件被环,這樣當(dāng)瀏覽器加載了頁面及腳本之后糙及,就能進(jìn)行路由跳轉(zhuǎn)了。
5. 動(dòng)態(tài)路由
上面提到的例子都是靜態(tài)路由筛欢,也就是說浸锨,路徑都是固定的。但是有時(shí)候我們需要在路徑中傳入?yún)?shù)版姑,例如獲取某個(gè)用戶的信息柱搜,我們不可能為每個(gè)用戶創(chuàng)建一條路由,而是在通過捕獲路徑中的參數(shù)(例如用戶id)來實(shí)現(xiàn)剥险。
例如在Express中:
app.get('/user/:id',(req,res,next)=>{// ... ...})
在Flask中:
@app.route('/user/')defget_user_info(user_id):pass
6. 嚴(yán)格路由
在很多情況下聪蘸,會(huì)遇到/foobar和/foobar/的情況,它們看起來非常類似表制,然而實(shí)際上有所區(qū)別健爬,具體的行為也是視服務(wù)器設(shè)置而定。
在Flask的文檔中么介,提到娜遵,末尾有斜線的路徑,類比于文件系統(tǒng)的一個(gè)目錄壤短;末尾沒有斜線的路徑魔熏,類比于一個(gè)文件衷咽。因此訪問/foobar的時(shí)候鸽扁,可能會(huì)重定向到/foobar/蒜绽,而反過來則不會(huì)。
如果使用的是Express桶现,默認(rèn)這兩者是一樣的躲雅,也可以通過app.set來設(shè)置strict routing,來區(qū)別對(duì)待這兩種情況骡和。