今天是四月十二號,距離上次寫博已經將近二十天了。一直忙於工做,回家被看書的時間佔用了。連續兩個禮拜被頻繁的足球籃球以及各類體育運動弄的精疲力竭,因此不多抽時間來寫技術博客。今天抽出時間把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作框架的時候,不建議使用低版本的瀏覽器。