Backbone源码解析(五):Route和History(路由)模块
今天是四月十二号,距离上次写博已经将近二十天了。一直忙于工作,回家被看书的时间占用了。连续两个礼拜被频繁的足球篮球以及各种体育运动弄的精疲力竭,所以很少抽时间来写技术博客。今天抽出时间把backbone的基本模块部分写完,还有一篇总结篇就结束这个系列了。昨天下了一整天的雨,天空放晴,外面一片清澈,索性来到大学城图书馆待会儿。别的就不扯了,下面开始进入正题。
这次我们将Router模块和history合并在一起,我们简称它们为路由器模块, 在backbone里面充当引路人的角色,它会监听浏览器里面的hash值的变化从而响应相对的事件。它的最基本原理是利用的了浏览器中的onhashchange或者pushstate监听事件。在我们讨论路由器模块之前,先让我们了解一些浏览器的url变化监听机制是怎么样的。以下是摘自网上的一段文字说明:
“在 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的具体使用:
接下来我们重点介绍history模块,它是实际上路由的承担者,内部的方法值得介绍。首先时构造构造函数:
e.History = function() {
this.handlers = [];
//绑定checkUrl的执行域一直在history中
f.bindAll(this, "checkUrl")
};
接下来是对其原型进行扩展:
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值是否有变化,然后进行跳转的动作。
然后是其他一些方法的说明:
//配置对应的路由方法,意思是将地址栏中的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做框架的时候,不建议使用低版本的浏览器。
Backbone源码解析(五):Route和History(路由)模块的更多相关文章
- Backbone源码解析(一):Event模块
Backbone是一个当下比较流行的MVC框架.它主要分为以下几个模块: Events, View, Model, Collection, History, Router等几大模块.它强制依赖unde ...
- Backbone源码解析(三):Collection模块
Collection模块式是对分散在项目中model的收集,他可以存储所有的model,构成一个集合,并且通过自身的方法统一操作model.Collection模块包装着若干对象,对象本身不具有一些方 ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- Celery 源码解析五: 远程控制管理
今天要聊的话题可能被大家关注得不过,但是对于 Celery 来说确实很有用的功能,曾经我在工作中遇到这类情况,就是我们将所有的任务都放在同一个队列里面,然后有一天突然某个同学的代码写得不对,导致大量的 ...
- ReactiveCocoa源码解析(五) SignalProtocol的observe()、Map、Filter延展实现
上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...
- ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现
上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...
- Spring 源码解析之DispatcherServlet源码解析(五)
spring的整个请求流程都是围绕着DispatcherServlet进行的 类结构图 根据类的结构来说DispatcherServlet本身也是继承了HttpServlet的,所有的请求都是根据这一 ...
- iOS即时通讯之CocoaAsyncSocket源码解析五
接上篇:iOS即时通讯之CocoaAsyncSocket源码解析四 原文 前言: 本文为CocoaAsyncSocket Read篇终,将重点涉及该框架是如何利用缓冲区对数据进行读取. ...
- MyBatis源码解析(四)——DataSource数据源模块
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634880.html 1.回顾 上一文中解读了MyBatis中的事务模块,其实事务操作无非 ...
- 深入源码解析类Route
微软官网对这个类的说明是:提供用于定义路由及获取路由相关信息的属性和方法.这个说明已经很简要的说明了这个类的作用,下面我们就从源码的角度来看看这个类的内部是如何工作的. public class Ro ...
随机推荐
- Ext.Net TreePanel 修改Icon图标
分类: Ext.Net2012-09-24 13:44 1779人阅读 评论(0) 收藏 举报 webformserverextassemblyxhtmlobject 1.默认icon 2.自定义ic ...
- 强大的Spring缓存技术(下)
基本原理 一句话介绍就是Spring AOP的动态代理技术. 如果读者对Spring AOP不熟悉的话,可以去看看官方文档 扩展性 直到现在,我们已经学会了如何使用开箱即用的 spring cache ...
- 123. Best Time to Buy and Sell Stock (三) leetcode解题笔记
123. Best Time to Buy and Sell Stock III Say you have an array for which the ith element is the pric ...
- IosPush推送通知的实现
1. Apple推送通知的机制 上图可以分为三个阶段: 第一阶段:应用程序把要发送的消息.目的iPhone的标识打包,发给APNS. 第二阶段:APNS在自身的已注册Push服务的iPhone列表中, ...
- 利用带关联子查询Update语句更新数据
Update是T-sql中再简单不过的语句了,update table set column=expression [where condition],我们都会用到.但update的用法不仅于此,真 ...
- python:threading多线程模块-创建线程
创建线程的两种方法: 1,直接调用threading.Thread来构造thread对象,Thread的参数如下: class threading.Thread(group=None, target= ...
- PHP那些非常有用却鲜有人知的函数
PHP里有非常丰富的内置函数,很多我们都用过,但仍有很多的函数我们大部分人都不熟悉,可它们却十分的有用.这篇文章里,我列举了一些鲜为人知但会让你眼睛一亮的PHP函数. levenshtein() 你有 ...
- redis迁移工具-redis-migrate-tool使用测试
https://github.com/vipshop/redis-migrate-tool一.安装redis-migrate-tool a.下载redis-migrate-tool软件包 https: ...
- Android Studio--Gradle基础(转)
原文链接:http://stormzhang.com/devtools/2014/12/18/android-studio-tutorial4/ 其实很早之前也写了一篇Gradle的基础博客,但是时间 ...
- shell 条件判断语句整理
常用系统变量 1) $0 当前程式的名称 2) $n 当前程式的第n个参数,n=1,2,…9 3) $* 当前程式的任何参数(不包括程式本身) 4) ...