Backbone源碼解析(五):Route和History(路由)模塊

  今天是四月十二號,距離上次寫博已經將近二十天了。一直忙於工做,回家被看書的時間佔用了。連續兩個禮拜被頻繁的足球籃球以及各類體育運動弄的精疲力竭,因此不多抽時間來寫技術博客。今天抽出時間把backbone的基本模塊部分寫完,還有一篇總結篇就結束這個系列了。昨天下了一成天的雨,天空放晴,外面一片清澈,索性來到大學城圖書館待會兒。別的就不扯了,下面開始進入正題。javascript

  此次咱們將Router模塊和history合併在一塊兒,咱們簡稱它們爲路由器模塊, 在backbone裏面充當引路人的角色,它會監聽瀏覽器裏面的hash值的變化從而響應相對的事件。它的最基本原理是利用的了瀏覽器中的onhashchange或者pushstate監聽事件。在咱們討論路由器模塊以前,先讓咱們瞭解一些瀏覽器的url變化監聽機制是怎麼樣的。如下是摘自網上的一段文字說明:java

  「在 URL 中 傳值有兩方式,一種是經過 search 來傳值(即 ? 後面的部分),一種是經過 hash 來傳值(即 # 後面的部分)。它們之間有一個區別, 即 search 改變時瀏覽器會發一次的 http request,而 hash 改變時瀏覽器不會發送 http request。也就是 說,search 能夠用來作瀏覽器和服務器端的信息傳遞,而 hash 則更適合用於本地頁面的信息傳遞正則表達式

在瞭解了這種監聽機制後,咱們開始來介紹一下router和histroy模塊的基本結構和設計思路瀏覽器

//router的構造函數
e.Router = function(a) {
	a || (a = {});
	/*爲routes賦值,a參數是一個對象的字面量,它裏面有不少函數 分別對應的是routes中的函數名。同時其中還有一個特殊的屬性是routes, 以名(hash跳轉名)和值(自定義參數名)爲基本機構。
	因此闖入的參數大概結構以下 a = { routes : { '/toPage1' : 'toPage1Fn', '/toPage2' : 'toPage2Fn'}, toPage1Fn : function(){ xxxxxx}, toPage2Fn : function(){}};
	*/
	if (a.routes) this.routes = a.routes;

	this._bindRoutes();
	//執行初始化函數
	this.initialize.apply(this, arguments)
};

  接下來是擴展router的原型,router只是一個表面上的工做者,它原始的方法不多,也比較簡單,只是作一些正則的驗證和一些方法的中轉(也便是調用history中的方法實現路由機制)。實際上的
路由機制都是用history模塊來完成的。所以,我再下面的方法中只作簡單的概要,而在history中對其中用到的方法會詳細講到。下面是router模塊中原始方法:服務器

f.extend(e.Router.prototype, e.Events, {
	initialize: function() {},
	//綁定初始化的值 即將屬性routes中的各個值和外層的function對應起來。a是正則表達式,若是不是則會進行轉換。
	route: function(a, b, c) {
		e.history || (e.history = new e.History);
		f.isRegExp(a) || (a = this._routeToRegExp(a));
		e.history.route(a, f.bind(function(d) {
			d = this._extractParameters(a, d);
			c.apply(this, d);
			this.trigger.apply(this, ["route:" + b].concat(d))
		},
		this))
	},
	//導航方法,手動更新url的hash值,a 是須要跳轉的hash值
	navigate: function(a, b) {
		e.history.navigate(a, b)
	},
	//將初始化傳入的參數作處理。
	_bindRoutes: function() {
		if (this.routes) {
			var a = [],
			b;
			for (b in this.routes) a.unshift([b, this.routes[b]]);
			b = 0;
			for (var c = a.length; b < c; b++) this.route(a[b][0], a[b][1], this[a[b][1]])
		}
	},
	//將a轉換爲正則
	_routeToRegExp: function(a) {
		a = a.replace(s, "\\$&").replace(q, "([^/]*)").replace(r, "(.*?)");
		return RegExp("^" + a + "$")
	},
	_extractParameters: function(a, b) {
		return a.exec(b).slice(1)
	}
});

  所以咱們在初始化router以前,必須先擴展它的原型對象,下面是router的具體使用:app

  接下來咱們重點介紹history模塊,它是實際上路由的承擔者,內部的方法值得介紹。首先時構造構造函數:框架

e.History = function() {
	this.handlers = [];
	//綁定checkUrl的執行域一直在history中
	f.bindAll(this, "checkUrl")
};

  接下來是對其原型進行擴展:ide

f.extend(e.History.prototype, {..........

  在擴展原型的方法中,只有一個(start)是對外的方法,它在history對象被實例化以後手動調用Backbone.History.start();,其餘都是供Router模塊調用的方法:函數

start: function(a) {
	if (m) throw Error("Backbone.history has already been started");
	this.options = f.extend({},
	{
		root: "/"
	},
	this.options, a);
	this._wantsPushState = !!this.options.pushState;
	this._hasPushState = !(!this.options.pushState || !window.history || !window.history.pushState);
	a = this.getFragment();
	var b = document.documentMode;
	if (b = t.exec(navigator.userAgent.toLowerCase()) && (!b || b <= 7)) this.iframe = g('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,
	this.navigate(a);
	this._hasPushState ? g(window).bind("popstate", this.checkUrl) : "onhashchange" in window && !b ? g(window).bind("hashchange", this.checkUrl) : setInterval(this.checkUrl, this.interval);
	this.fragment = a;
	m = !0;
	a = window.location;
	b = a.pathname == this.options.root;
	if (this._wantsPushState && !this._hasPushState && !b) return this.fragment = this.getFragment(null, !0),
	window.location.replace(this.options.root + "#" + this.fragment),
	!0;
	else if (this._wantsPushState && this._hasPushState && b && a.hash) this.fragment = a.hash.replace(j, ""),
	window.history.replaceState({},
	document.title, a.protocol + "//" + a.host + this.options.root + this.fragment);
	if (!this.options.silent) return this.loadUrl()
},

  說明:start 初始化函數,實例化history模塊後當即執行此函數,它作了以下幾件事情:
    一:設置一些基本的屬性,好比是否啓用pushState方法做爲默認的監聽方法,還有基礎路徑root的設置,root在默認的狀況下根目錄。還有是否渲染界面的slient屬性。
    二:綁定hash變化的方法:將checkUl的方法綁定到onhashchange上,以便監聽瀏覽器的地址的變化。
    三:對瀏覽器的兼容處理:對於沒有pushState事件或者onhashchange事件的瀏覽器其,好比說早期的Ie版本,它會用setInterval來解決hash變化。是爲了記錄兩次不一樣的hash值得變化,bk的作法是將上一次的hash值保存到一個iframe中,待下次改變hash值改變的時候,咱們能夠從iframe中取值與後一次的對比,這樣就能夠判斷hash值是否有變化,而後進行跳轉的動做。this

  而後是其餘一些方法的說明:

//配置對應的路由方法,意思是將地址欄中的hash值和自定義的方法對應起來。此方法供router模塊中的同名troute方法調用。
route: function(a, b) {
	this.handlers.unshift({
		route: a,
		callback: b
	})
},
//偵測地址欄中的地址變化。而且放回對應的觸發函數。這時候它就去iframe中取保存的hash值 來和當前的hash值作對比。
checkUrl: function() {
	var a = this.getFragment();
	a == this.fragment && this.iframe && (a = this.getFragment(this.iframe.location.hash));
	if (a == this.fragment || a == decodeURIComponent(this.fragment)) return ! 1;
	this.iframe && this.navigate(a);
	this.loadUrl() || this.loadUrl(window.location.hash)
},
//在this.handlers中找到hash變化的對應函數而且返回。
loadUrl: function(a) {
	var b = this.fragment = this.getFragment(a);
	return f.any(this.handlers,
	function(a) {
		if (a.route.test(b)) return a.callback(b),
		!0
	})
},
//手動改變hash值(即瀏覽器中對應的地址),導航到某個方法或者界面。a是你須要傳入的hash值;
navigate: function(a, b) {
	var c = (a || "").replace(j, "");
	if (! (this.fragment == c || this.fragment == decodeURIComponent(c))) {
		if (this._hasPushState) {
			var d = window.location;
			c.indexOf(this.options.root) != 0 && (c = this.options.root + c);
			this.fragment = c;
			window.history.pushState({},
			document.title, d.protocol + "//" + d.host + c)
		} else if (window.location.hash = this.fragment = c, this.iframe && c != this.getFragment(this.iframe.location.hash)) this.iframe.document.open().close(),
		this.iframe.location.hash = c;
		b && this.loadUrl(a)
	}
}

  還有一個getFragment方法是放回當前瀏覽器中的hash值。在此不作介紹。總的來講History模塊作的事監聽瀏覽器中url的變化而後調用對應的函數。它的流程大概就是這樣的:

第一步:

第二步改變地址:

或者:

或者:

而後onhashchange響應,回掉page2對應的pageFn方法來進行模塊之間的互動.

小提示:若是瀏覽器不支持任何原始的url響應事件,那麼setInterval會不間斷地(50ms)刷新監控它的變化,這樣作顯然是不合理的退而求其次的方法。因此在選用bk作框架的時候,不建議使用低版本的瀏覽器。

相關文章
相關標籤/搜索