Backbone源码解析(五):Route和History(路由)模块

时间:2022-07-16 09:19:10

  今天是四月十二号,距离上次写博已经将近二十天了。一直忙于工作,回家被看书的时间占用了。连续两个礼拜被频繁的足球篮球以及各种体育运动弄的精疲力竭,所以很少抽时间来写技术博客。今天抽出时间把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的具体使用:

Backbone源码解析(五):Route和History(路由)模块

  接下来我们重点介绍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的变化然后调用对应的函数。它的流程大概就是这样的:

第一步:

Backbone源码解析(五):Route和History(路由)模块

第二步改变地址:

Backbone源码解析(五):Route和History(路由)模块

或者:

Backbone源码解析(五):Route和History(路由)模块

或者:

Backbone源码解析(五):Route和History(路由)模块

然后onhashchange响应,回掉page2对应的pageFn方法来进行模块之间的互动.

小提示:如果浏览器不支持任何原始的url响应事件,那么setInterval会不间断地(50ms)刷新监控它的变化,这样做显然是不合理的退而求其次的方法。所以在选用bk做框架的时候,不建议使用低版本的浏览器。

Backbone源码解析(五):Route和History(路由)模块的更多相关文章

  1. Backbone源码解析(一):Event模块

    Backbone是一个当下比较流行的MVC框架.它主要分为以下几个模块: Events, View, Model, Collection, History, Router等几大模块.它强制依赖unde ...

  2. Backbone源码解析(三):Collection模块

    Collection模块式是对分散在项目中model的收集,他可以存储所有的model,构成一个集合,并且通过自身的方法统一操作model.Collection模块包装着若干对象,对象本身不具有一些方 ...

  3. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  4. Celery 源码解析五: 远程控制管理

    今天要聊的话题可能被大家关注得不过,但是对于 Celery 来说确实很有用的功能,曾经我在工作中遇到这类情况,就是我们将所有的任务都放在同一个队列里面,然后有一天突然某个同学的代码写得不对,导致大量的 ...

  5. ReactiveCocoa源码解析&lpar;五&rpar; SignalProtocol的observe&lpar;&rpar;、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

  6. ReactiveSwift源码解析&lpar;五&rpar; SignalProtocol的observe&lpar;&rpar;、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

  7. Spring 源码解析之DispatcherServlet源码解析&lpar;五&rpar;

    spring的整个请求流程都是围绕着DispatcherServlet进行的 类结构图 根据类的结构来说DispatcherServlet本身也是继承了HttpServlet的,所有的请求都是根据这一 ...

  8. iOS即时通讯之CocoaAsyncSocket源码解析五

    接上篇:iOS即时通讯之CocoaAsyncSocket源码解析四         原文 前言: 本文为CocoaAsyncSocket Read篇终,将重点涉及该框架是如何利用缓冲区对数据进行读取. ...

  9. MyBatis源码解析(四)——DataSource数据源模块

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634880.html 1.回顾 上一文中解读了MyBatis中的事务模块,其实事务操作无非 ...

  10. 深入源码解析类Route

    微软官网对这个类的说明是:提供用于定义路由及获取路由相关信息的属性和方法.这个说明已经很简要的说明了这个类的作用,下面我们就从源码的角度来看看这个类的内部是如何工作的. public class Ro ...

随机推荐

  1. php课程---JavaScript改变HTML中的元素

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. WEB三层架构与MVC

    web三层架构是指: >用户接口层(UI Layer) >业务逻辑层(Bussiness Layer) >持久化层 关于业务逻辑和用户接口 在早期的web开发中,因为业务比较简单,并 ...

  3. Error Domain&equals;com&period;alamofire&period;error&period;serialization&period;response Code&equals;-1016 &quot&semi;Request failed&colon; unacceptabl

    在使用AFNetworking 2.0  的时候本来一切很顺畅,但是中途遇到几个比较坑的地方 这里分享一下爬坑经历,忘读者不能速爬坑! 在发送请求后,NSURLSessionDataTask一直报错 ...

  4. 浏览器的CSS Hacks

    LZ注:此文原作者是:Paul Irish(Google的前端开发工程师),本文是原文的部分译文. 我不再使用CSS Hacks了,相反的是,我将使用IE的条件判断将类应用到body标签.   但是, ...

  5. POJ 1113 Wall 求凸包的两种方法

    Wall Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 31199   Accepted: 10521 Descriptio ...

  6. Delphi 把字符串读到流中的操作。

    var FReQuestM := TMemoryStream FReQuestM.Write(PChar(FcVoucherXML)^, Length(FcVoucherXML)); 这样就读到流中了 ...

  7. MVC4不支持EF6解决方案 &amp&semi;&amp&semi; Nuget控制台操作说明

    问题背景:MVC4不支持EF6,所以要把EF6卸载然后安装EF5.只能降低版本EF5+MVC4或者EF6+MVC5; 这时候: Uninstall-Package EntityFramework -F ...

  8. Java 通过get post 请求url

    1️⃣.已获取小程序的access_token 为例,通过Get请求url import com.alibaba.fastjson.JSONObject; String wechatUrl = &qu ...

  9. python23的区别-日常记录

    1. xrange:python3 中取消了range函数,把python2中的xrange重新命名为range,所以在python3中直接用range就行. 2. print:python3中pri ...

  10. 【转】使用SecureCRT连接ubuntu

    1.  Ubuntu 装好之后默认是没有安装ssh服务的(我的版本是Ubuntu 12.04.3 LTS),需要手动安装:  安装命令:sudo apt-get install openssh-ser ...