backbone的路由分两部分。其中一个是路由配置router,另外一个是和路由相关的history,用作浏览器的前进后退等。
先看下histroy部分。
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 设置默认状态是路由未开启
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变化
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监听
// 停止历史支持 stop: function() { $(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl); clearInterval(this._checkUrlInterval); History.started = false; },
3 添加路由映射
// 导航到相应的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; } }