javascript基礎修煉(6)——前端路由的基本原理
【造輪子】是筆者學習和理解一些較複雜的程式碼結構時的常用方法,它很慢,但是效果卻勝過你讀十幾篇相關的文章。為已知的API方法自行編寫實現,遇到自己無法復現的部分再有針對性地去查資料,最後當你再去學習官方程式碼的時候,就會明白這樣做的價值,總有一天,你也將有能力寫出大師級的程式碼。
一. 前端路由
現代前端開發中最流行的頁面模型,莫過於 SPA
單頁應用架構。單頁面應用指的是應用只有一個主頁面,通過動態替換DOM內容並同步修改url地址,來模擬多頁應用的效果,切換頁面的功能直接由前臺指令碼來完成,而不是由後端渲染完畢後前端只負責顯示。前端三駕馬車 Angular
, Vue
, React
均基於此模型來執行的。 SPA
能夠以模擬多頁面應用的效果,歸功於其 前端路由機制 。
前端路由,顧名思義就是一個 前端不同頁面的狀態管理器 ,可以不向後臺傳送請求而直接通過前端技術實現多個頁面的效果。angularjs中的 ui-router
,vue中的 vue-router
,以及react的 react-router
均是對這種功能的具體實現。
既然 前端路由
這麼牛逼,那必須的好好研究一下。
二. 兩種實現方式及其原理
常見的路由外掛中兩種方式都是支援且可以切換的,例如 angularjs1.x
中就可以通過以下程式碼從 Hash 模式切換到 H5 模式:
$locationProvider.html5Mode(true);
切換到 HTML5 的路由模式,主要用於避免url地址中包含 # 而引發的問題。
1.HashChange
1.1 原理
HTML
頁面中通過錨點定位原理可進行無重新整理跳轉,觸發後url地址中會多出 # + 'XXX'
的部分,同時在全域性的 window
物件上觸發 hashChange
事件,這樣在頁面錨點雜湊改變為某個預設值的時候,通過程式碼觸發對應的頁面DOM改變,就可以實現基本的路由了,基於 錨點雜湊
的路由比較直觀,也是一般前端路由外掛中最常用的方式。
1.2 應用
下面通過一個例項看一下,當點選 angularjs
的連線時,可以看到控制檯打印出了相應的資訊。


2.HTML5 HistoryAPI
2.1 原理
HTML5
的 History API
為瀏覽器的全域性 history
物件增加的擴充套件方法。 一般用來解決ajax請求無法通過 回退
按鈕回到請求前狀態的問題 。
在HTML4中,已經支援 window.history
物件來控制頁面歷史記錄跳轉,常用的方法包括:
- history.forward() ; //在歷史記錄中前進一步
- history.back() ; //在歷史記錄中後退一步
- history.go(n) : //在歷史記錄中跳轉n步驟,n=0為重新整理本頁,n=-1為後退一頁。
在HTML5中, window.history
物件得到了擴充套件,新增的API包括:
- history.pushState(data[,title][,url]) ;//向歷史記錄中追加一條記錄
- history.replaceState(data[,title][,url]) ;//替換當前頁在歷史記錄中的資訊。
- history.state ;//是一個屬性,可以得到當前頁的state資訊。
- window.onpopstate ;//是一個事件,在點選瀏覽器後退按鈕或js呼叫forward()、back()、go()時觸發。監聽函式中可傳入一個event物件,event.state即為通過pushState()或replaceState()方法傳入的data引數。
2.2 應用
瀏覽器訪問一個頁面時,當前地址的狀態資訊會被壓入 歷史棧
,當呼叫 history.pushState()
方法向歷史棧中壓入一個新的 state
後,歷史棧頂部的指標是指向新的 state
的。可以將其作用簡單理解為 假裝已經修改了url地址並進行了跳轉 ,除非使用者點選了瀏覽器的 前進
, 回退
,或是顯式呼叫HTML4中的操作歷史棧的方法,否則不會觸發全域性的 popstate
事件。
在下面的示例中,點選導航按鈕,可以看到 url
位址列發生了變化,且控制檯打印出了響應的資訊。


3.hash 和 history API對比
對比 | hash路由 | History API 路由 |
---|---|---|
url字串 | 醜 | 正常 |
命名限制 | 通常只能在同一個 document 下進行改變 |
url地址可以自己來定義,只要是同一個域名下都可以,自由度更大 |
url地址變更 | 會改變 | 可以改變,也可以不改變 |
狀態儲存 | 無內建方法,需要另行儲存頁面的狀態資訊 | 將頁面資訊壓入歷史棧時可以附帶自定義的資訊 |
引數傳遞能力 | 受到url總長度的限制, | 將頁面資訊壓入歷史棧時可以附帶自定義的資訊 |
實用性 | 可直接使用 | 通常服務端需要修改程式碼以配合實現 |
相容性 | IE8以上 | IE10以上 |
三.親手造一個簡單的前端路由外掛
造輪子,不是為了把它裝在你的車上,而是當你在荒郊野外開車而輪子出了問題時多一種選擇。
接下來就自己動手實現一個前端路由的外掛吧~
3.1基於Hash的前端路由外掛 myHashRouter.js
我們希望實現的功能是:
- 1.引入
MyHashRouter.js
庫 - 2.通過
when()
方法來定義若干不同的路由狀態 - 3.通過
init()
方法啟動路由功能 - 4.通過點選導航實現 前端路由 切換
首先編寫js骨架,如圖所示:
;(function() { function Router() { //記錄路由的跳轉歷史 this.historyStack = []; //記錄已註冊的路由資訊 this.registeredRouter = []; //路由匹配失敗時跳轉項 this.otherwiseRouter = { path: '/', content: 'home page' } } /* * 啟動路由功能 */ Router.prototype.init = function() { } /* * 繫結window.onhashchange事件的回撥函式 */ Router.prototype._bindEvents = function() { } /** * 路由註冊方法 */ Router.prototype.when = function(path, content) { } /** * 判斷新新增的路由是否已存在 */ Router.prototype._hasThisRouter = function(path) { } /** * 路由不存在時的指定地址 */ Router.prototype.otherwise = function(path, content) { } /** * 路由跳轉方法,主動呼叫時可用於跳轉路由 */ Router.prototype.go = function(topath) { } /** * 用於將對應路由資訊渲染至頁面,實現路由切換 */ Router.prototype.render = function (content) { } var router = new Router(); //將介面暴露至全域性 window.$router = router; })();
完成了路由外掛的編寫後,我們在demo中引入該庫,然後使用 when()
方法註冊幾個路由地址,再使用 init()
方法啟動路由,指令碼部分程式碼如下:

效果:
執行附件中的 router-demo-hash.html
,點選導航按鈕,即可看到url位址列以及內容區域同步更改。

3.2基於History API的前端路由外掛 myHistoryRouter.js
由於 History API
不支援低於IE10以下版本的瀏覽器(其他大多數現代瀏覽器基本都支援),所以我們在 init()
方法啟動時先進行可用性判斷,基本程式碼框架與基於 Hash
的路由外掛一致。每個方法的實現並不難寫,這裡不再贅述,筆者自己的程式碼實現放在附件 myHashRouter.js
中,水平有限,僅供參考。

3.3整合說明
為方便理解,本例中將兩種模式分開編寫,如果是外掛庫的開發,可以模仿 ui-router 增加一個 html5mode()
的方法,在 init()
方法啟動路由時,根據所傳的引數生成不同的路由外掛的單例,也就是我們常說的 工廠模式 來實現即可。
四.後記
-
造車輪
是一個很好的學習方式,雖然自己造的車輪很簡陋,但是對於理解工具的底層原理卻很有幫助。 - 本例只是編寫了一個路由工具的基本骨架,真正的路由工具還需要做很多功能擴充套件,個別功能的複雜度也會很高,例如 路徑的正則匹配 , 懶載入 , 組合檢視 , 巢狀檢視 , 路由動畫 等等,有興趣的小夥伴可以在本例提供的框架上進行學習擴充套件。
- 附件說明:
- index_h5history.html —— history API基本用法演示demo
- index_hashchange.html —— hashchange基本用法演示demo
- router-demo-hash.html —— 引用了
myHashRouter.js
的demo - myHashRouter.js —— 自己開發的基於hash簡易路由外掛
- router-demo-hash.html —— 引用了
myHashRouter.js
的demo - myHistoryRouter.js —— 自己開發的基於historyAPI的簡易路由外掛
- router-demo-history.html —— 引用了
myHistoryRouter.js
的demo