SPA的成功離開不這三個東西,分層架構,路由系統,儲存系統。分層架構是我們組織復雜代碼的關鍵,這里特指MVVM的avalon;路由系統是將多個頁面壓縮在一個頁面的關鍵;儲存系統特指本地儲存,是安全保存大量數據的關鍵。本章節介紹的是avalon三柱臣之一的mmRouter(內含mmHistory)。
我們先上一個示例吧。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>路由系統</title>
<script src="avalon.js"></script>
<script>
require(["mmRouter"], function() {
var model = avalon.define({
$id: "test",
currPath: "",
params: {},
query: {},
args: "[]"
})
function callback() {
model.currPath = this.path
var params = this.params
if ("time" in params) {
params.time = avalon.filters.date(params.time, "yyyy年M月dd日")
}
model.params = params
model.args = "[" + [].slice.call(arguments).join(",") + "]"
model.query = this.query
}
avalon.router.get("/aaa/", callback)
avalon.router.get("/bbb", callback)
avalon.router.get("/ccc/:ccc", callback)
avalon.router.get("/ddd/{time:date}/", callback)
avalon.router.get("/eee/{count:\\d{4}}/", callback)
avalon.router.get("/fff", callback)
avalon.history.start({
basepath: "/avalon"
})
avalon.scan()
})
</script>
</head>
<body >
<div ms-controller="test">
<table width="100%" height="300">
<tr>
<td width="250">
<ul>
<li><a href="#!/aaa">aaa</a></li>
<li><a href="#!/bbb?uu=3445345&were=4324">bbb</a></li>
<li><a href="#!/ccc/etretr">ccc</a></li>
<li><a href="#!/ddd/2014-09-19">ddd</a></li>
<li><a href="#!/eee/2222">eee</a></li>
<li><a href="#!/fff?a=1&nn=4&dfg=676">fff</a></li>
</ul>
</td>
<td>
<div style="color:red">this.path: {{currPath}}</div>
<div style="color:blue">arguments: {{args}}</div>
<fieldset>
<legend>this.params</legend>
<ol>
<li ms-repeat="params"> {{$key}}: {{$val}}</li>
</ol>
</fieldset>
<fieldset>
<legend>this.query</legend>
<ol>
<li ms-repeat="query"> {{$key}}: {{$val}}</li>
</ol>
</fieldset>
</td>
</tr>
</table>
<div style="height: 600px;width:1px;">
</div>
<p id="eee">會定位到這里</p>
</div>
</body>
</html>

眼見為實,可以看到mmRouter很好地在IE6下運行,對於高版本的瀏覽器更不在話下。回退按鈕,hash如果與頁面上的某個錨記同名,它也會自動定拉到那里去。
我們接着詳細介紹一下它的用法吧。
- 先引入mmRouter(請將mmRouter.js、mmHistory.js這兩個文件與avalon.js放在一起)
- 定義VM
- 定義路由規則
- 啟動歷史管理器
- 開始掃描
mmHistory是用於歷史管理,它會劫持頁面上所有點擊鏈接的行為,當這些鏈接是以#/ 、#!/開頭,就嘗試匹配路由規則,阻止頁面刷新(通過hash方式或HTML5的replaceState方式)。mmRouter是給我們定義路由規則,路由規則可以更精細地指定每個參數(param)的匹配規則,如果符合就執行對應的回調,如果不符合,就進入error回調。
當用戶點擊頁面鏈接時,路址欄會發生變化,avalon是參考了angular的方式來處理路址欄。 
- Hashbang模式(默認), 這個模式下所有瀏覽器都支持
- HTML5模式, 這個只能應用於firefox, chrome, safari,IE10+,如果瀏覽器不支持此特性,即使你設置avalon.history.start({html5Mode:true}),它也是在Hashbang模式下運行。
當我們使用history API mode的時候,我們對於不同的瀏覽器,需要不同的鏈接, 但我們只需要提供其hash部分就行了,例如<a href=“#/aaa”>link</a>
當用戶單擊這個超鏈接時:
- 在Hashbang mode中,URL會改為/index.html#!/aaa
- 在HTML5 mode中,URL會改為/index.html/aaa
你會發現,Hashbang mode中的#!后面的部分等於HTML5 mode中的除域名外的所有部分。而#!其實是google的 _escaped_fragment_ 爬抓規則, 方便我們的AJAX應用也能被爬蟲收錄。當然,我們還需要在head標簽內添加一個meta標簽:
<meta name="fragment" content="!" />
想了解更多關於這個技術的信息,可以查看這里 下面是路由器的API列表:
- avalon.history.start(opts), 開始監聽URL變化,opts。

- avalon.history.stop(), 中止監聽URL變化。
- avalon.router.get(path, callback),用於添加路由規則。第一個為路由規則,如"/aaa", "/bbb/:bbbId","/eee/{eeeId}/ddd/{dddId:[0-9]{6}}" 冒號后的東西或花括號的東西表示為參數,花括號模式下還可以指定匹配規則。callback為回調函數,框架會將冒號后的或花括中的匹配內容傳進來,此外this對象,包含了path、 params、 query等對象與屬性。
- avalon.router.add(method, path, callback) , 添加回調,第一個為請求類型,如GET,POST,DELETE什么, 第2個為路由規則,第3個為回調函數
- avalon.router.error(callback),如果沒有一條路由規則滿足此請求,那么就轉交此回調處理,我們可以在里面寫跳轉到404頁面這樣的邏輯
- avalon.router.navigate(path),強制觸發對應路徑的回調
- avalon.router.setLastPath(path) , 這是框架自己調用,保存最近一次跳轉的路徑
- *avalon.router.getLastPath() *,取得最近一次跳轉的路徑,比如用戶F5強制頁面,你在ready回調中執行此方法,得到path,然后將它放進navigate中就能回到原來的頁面了。
最后我們再詳細介紹一下路由規則,它是一個字符串,必須以/開頭,它可以在每個/后直接接冒號,表示這之后到下一個斜線或末尾是一個參數。它也可以使用花括號表示里面的內容也一個參數。斜錢風格是來自backbone與express的,花括號風格是來自ui-router。花括號風格更為強大些,因此它可以指定更詳細的匹配規則。
avalon.router.get("/ddd/:dddID/",callback)
avalon.router.get("/ddd/{dddID}/",callback)
avalon.router.get("/ddd/{dddID:[0-9]{4}}/",callback)
avalon.router.get("/ddd/{dddID:int}/",callback)
mmRouter默認有四個參數匹配器,分別叫做date, init, bool, string。
$types: {
date: {
pattern: "[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])",
decode: function(val) {
return new Date(val.replace(/\-/g,"/"))
}
},
string: {
pattern: "[^\/]*"
},
bool: {
decode: function(val) {
return parseInt(val, 10) === 0 ? false : true;
},
pattern: "0|1"
},
int: {
decode: function(val) {
return parseInt(val, 10);
},
pattern: "\d+"
}
}
decode方法是用來將我們從地址欄抽取出來的那些小字符串轉換為想要的類型或格式。如果你想要更多效果,還可以自己添加,如avalon.router.$type.d4 = { pattern: ’[0-9]{4}’, decode: Number}
avalon.router.get("/ddd/{dddID:d4}/",callback)
總而言之,mmRouter是遠遠比angular自帶路由器與backbone那個強大N多。未來還會進一步開發類似ui-router那樣基於狀態管理的路由器,敬請期待!
