zoukankan      html  css  js  c++  java
  • 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做框架的时候,不建议使用低版本的浏览器。

  • 相关阅读:
    计算直线的交点数
    不容易系列之(4)——考新郎
    神、上帝以及老天爷
    N!
    Number Sequence
    33_ABB机器人智能周期保养与复位操作
    34_WorldZone区域监控功能的使用
    三菱PLC(FX3U)的模拟量应用
    第19集 PLC盒子的使用
    第18集 使用黑盒设计创建宏文件
  • 原文地址:https://www.cnblogs.com/constantince/p/4419919.html
Copyright © 2011-2022 走看看