backbone的路由分兩部分。其中一個是路由配置router,另一個是和路由相關的history,用做瀏覽器的前進後退等。javascript
先看下histroy部分。java
1 首先,初始化路由配置數組,而後綁定checkurl上下文對象是backbone數組
this.handlers = []; _.bindAll(this, 'checkUrl');
2 全局對象是window的狀況瀏覽器
// Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; }
3 緩存幾個正則,第一個是#或 / 開頭, 或空白結尾; 第二個是 /開頭 或 / 結尾; 第三個是 / 結尾緩存
// Cached regex for stripping a leading hash/slash and trailing space.路由修正,開頭的/,#或結尾的空格 var routeStripper = /^[#\/]|\s+$/g; // Cached regex for stripping leading and trailing slashes.跟路徑修正 var rootStripper = /^\/+|\/+$/g; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; // Cached regex for removing a trailing slash.末尾的/修正 var trailingSlash = /\/$/;
4 設置默認狀態是路由未開啓ide
History.started = false;
下面是原型的一些關鍵方法函數
History.prototype = { interval: 50, atRoot: function() { return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root; //判斷pathname後面加上/, 與this.root是不是全等的,是就說明在跟目錄下 }, getHash: function(window){ var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; },
getFragment: function(fragment, forcePushState){ if(fragment === null){ if(this._hasPushState || !this._wantsHashChange || forcePushState){ //支持pushState, 或hashchange, 或強制性的支持pushState fragement = this.location.pathname; //取出url的pathname就是fragment var root = this.root.replace(trailingSlash, '');//root去掉尾部的/ if(!fragement.indexOf(root)){ fragement = fragement.substr(root.length);//若是fragment中沒有root路徑,那就讓出前面的root路徑的長度的位置????這塊還有疑問 } }else{ fragement = this.getHash();//沒提供參數,而且不支持pushState,則取到#錨點 } } return fragement.replace(routeStripper, '');//提供fragment參數,則進行路由修正 }
1 原型中其餘條件準備好,開始監聽hash變化ui
start: function(options){ if (History.started) throw new Error("Backbone.history has already been started"); History.started = true; //若是歷史紀錄已經處於開啓狀態,拋出錯誤。不僅跟實例有關,還與構造函數自己的屬性相關 this.options = $.extend({root: '/'}, this.options, options); //擴展配置,設置默認root是/,擴展this的配置以及start傳入的配置 this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; //hashChange不明顯設置爲false,則默認想要hashChange this._wantsPushState = !! this.options.pushState; this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState) //是否支持pushState,取決於設置,而且是否有瀏覽器的歷史紀錄支持 var fragment = this.getFragment(); var docMode = document.documentMode;//documentMode是一個IE的私有屬性,在IE8+中被支持。 this.root = ('/' + this.root + '/').replace('rootStripper', '/');//給root加上先後/而且修正爲只有一個 if(this._hasPushState){ $(window).on('popstate', this.checkUrl);//支持pushState } else if(this._wantsHashChange && ('onhashchange' in window) && !oldIE){//支持hashchange $(window).on('hashchange', this.checkUrl); } else if(this._wantsHashChange){ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);//都不支持 } this.fragement = fragement; var loc = this.location; // If we've started off with a route from a `pushState`-enabled browser, // but we're currently in a browser that doesn't support it... // 兼容性處理 參數設置與當前瀏覽器支持狀況衝突的時候 if(this._wantsHashChange && this._wantsPushState){//既須要hashchange也須要pushstate if(!this._hasPushState && !this.atRoot()){ //不在跟目錄,而且不支持pushState this.fragement = this.getFragment(null, true); //取出fragment,而且強制要求pushstate this.location.replace(this.root + '#' + this.fragement); //給出地址,讓瀏覽器本身處理 return true; }else if(this._hasPushState && this.atRoot() && loc.hash){ //若是是錨點導航而且在跟目錄裏面的,用錨點 this.fragement = this.getHash().replace(routeStripper, ''); this.history.replaceState({}, document.title, this.root + this.fragement); } } if(!this.options.silent){ return this.loadUrl(); } }
2 中止history監聽this
// 中止歷史支持 stop: function() { $(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl); clearInterval(this._checkUrlInterval); History.started = false; },
3 添加路由映射url
// 導航到相應的route地址。。。添加fragment改變時候,須要檢查的路由。 // 這裏用handlers隊列處理, 防止快速的改變地址可是沒處理完成 引發的問題 route: function(route, callback) { this.handlers.unshift({route: route, callback: callback}); },
4 檢查url是否改變,改變則loadurl
// 檢查url 兼容性處理 checkUrl: function(e) { var current = this.getFragment(); if (current === this.fragment) return false; this.loadUrl() || this.loadUrl(this.getHash()); },
// load當前的URL片斷 若是真的有相應的route地址處理函數 則執行它 loadUrl: function(fragmentOverride) { var fragment = this.fragment = this.getFragment(fragmentOverride); var matched =this.handlers.some(function(handler, index, array) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; } }); return matched; },
5 設置導航,更新hash
// 導航 根據url片斷導航去相應的畫面 兼容性處理 navigate: function(fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: options}; fragment = this.getFragment(fragment || ''); if (this.fragment === fragment) return; this.fragment = fragment; var url = this.root + fragment; // If pushState is available, we use it to set the fragment as a real URL. if (this._hasPushState) { this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); // If hash changes haven't been explicitly disabled, update the hash // fragment to store history. } else if (this._wantsHashChange) { this._updateHash(this.location, fragment, options.replace); // If you've told us that you explicitly don't want fallback hashchange- // based history, then `navigate` becomes a page refresh. } else { return this.location.assign(url); } if (options.trigger) this.loadUrl(fragment); }, // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. // 更新hash值 包含替換當前hash 或者是增長曆史到瀏覽器的歷史記錄中 _updateHash: function(location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); } else { // Some browsers require that `hash` contains a leading #. location.hash = '#' + fragment; } }