注意:强烈建议一边阅读源码一边阅读本文。
终于到了backbone
源码解读的最后一篇,这一篇和前面几篇时间上有一定的间隔(因为要回学校有一堆乱七八糟的事...)。在这一篇里面会讲解Bakcbone
的sync
& router
& histrory
。sync
比较简单,但是路由的部分就比较复杂了。个人觉得是整个backbone
源码里面最不好懂的一个部分,这个部分也使得backbone
可以方便实现可以“返回”的单页面应用。个人觉得这个部分其实并没有很MVC
有很密切的关系,但是它非常重要。读过源码就会发现,其实这一个部分与其他的模块(Model
,Collection
& View
)相对独立。如果不希望使用backbone
但希望能用到这个路由系统的话估计拆出来难度也不会很大。
总体来说路由模块由Router
和Histrory
组成,而Router
事实上可以看成是对History
的一个封装。用户直接操作的部分时Router
,相关函数的处理(路由匹配时调用的函数)也是在Router
中完成。History
主要是处理一个更加棘手的问题,就是有关链接的问题。这里面有关于跨浏览器的解决方案是非常值得学习的。
1. Router
Router
是对History
的封装,也是给用户定义路由的接口。一般来说,用户在使用Router
的时候会定义一个routers
的对象,里面是想关路由与处理函数组成的key-value
。Router
代码中主要做两件事,一件是对正则表达式的操作(可以看见里面很多令人痛苦的正则表达式),另一件事就是事件相关的绑定了。Router
的相关代码不多,写得比较精简。
1.1 函数绑定与执行
Router
里面定义了两种方法来定义路由事件,一个方法是用户定义routers
,通过初始化调用_bindRoutes
函数;一个方法是用自带的route
函数。其实两个方法本质上都是调用了route
函数,_bindRoutes
里面实际上也是通过循环来调用route
函数。因此路由函数重点在于route
函数。
在route
函数里面,一开始是进行一些参数的整理。然后就是调用了History
模块的route
函数,把正则(匹配参数用的)和一个回调函数传了进去。在回调函数里面就是做 执行函数
——触发router的事件
——触发history的事件
这几个步骤,在History
的route
函数里面,只是简单的插入地把key
和callback
组成对象插入handlers
数组里面而已。
1.2. 正则表达式转化与使用
Router
函数里面最难懂也非常重要的部分是格式的转换。在Router
里面有两个重要的函数_routeToRegExp
函数和_extractParameters
两个,这两个函数与正则密切相关。
_extractParameters
函数的作用是利用正则表达式,把传进来的url
片段fragment
分割成片段存进数组当中。这些片段是真实的已经匹配出来的参数,在route
函数里面会把这些参数传给用户定义的函数里面,供用户使用。
_routeToRegExp
函数是一个简单,但要完全理解很难的函数。这个函数的作用就是返回一个RegExp
对象,通过这一个对象来匹配当前的链接,然后从中得到参数。进入这个函数之后会通过字符串的replace
函数,匹配出路由的是哪几(或一)种情况,并且替代成可以捕获参数的正则字符串。比如说把路由定义里的/:page
或者*fragment
这样的字符串通过事先定义好的几个正则匹配到,然后换成带()
的可以捕获的形式,然后在创建正则去捕获真正需要的常数。
2. History
backbone
中对于History
模块的使用是通过用构造方式调用(new)
返回一个可以使用prototype
方法的对象来实现的。Backbone.history = new History;
这个模块非常重要,而且在整个backbone
里面可以说是最难完全读懂的。下面我会从三个方面来讲:一个方面是有关于路径格式处理的问题,在这方面也有很多和正则表达式相关的函数;另一个方面是最关键的一个方面,就是History
检测浏览器来使用不同的路由控制方式;最后一个方面就是通过具体的函数来讲解它是如何实现第二点各方面所说的控制的。
History
从接口的角度来说有start
函数作为初始化的设置,还有通过Router
模块封装的navigate
方法。Router
里面的很多处理需要调用到这个模块的方法。
History
事实上也是对location/history
一定程度上的封装。很多时候是通过location
模块来读取匹配,通过history
的一些方法来进行路由控制。
2.1 路径格式处理
2.1.1 root
用户可以设置root
,作为根路径。这个根路径在模块中有一些判断和处理的地方。比方说确定当前是否在根路径,或者在当前URL
提取出相应的锚点等等都需要用到root
这个内部变量。
2.1.2 getFragment
在这个函数里面我们可以看到URL
的格式分为了两种。一种是hash
方式,一种是search
方式(主要是兼容较老的浏览器)。在这里通过判断来进行浏览器能力检测。对于大部分现代浏览器来说,事实上大都是使用hash
方式获取锚点#
后面的URL
片段。
2.2 路由控制的三种方法(核心)
2.2.1 onhashchange方式
这种方式是通过监听'hashchange'
事件,然后触发事件,用location.hash.replace
方法来改变路由。
2.2.2 pushstate方式
这种方式是最为推荐的HTML5
方式。使用history
的pushState
方法修改history
里的记录,然后也可以通过监听popstate
来触发一些相关的事件。
2.2.3 iframe方式
这是一个非常巧妙但是从某种程度上非常“丑陋”的方法。丑陋是在它比较吃性能,一方面它有很多dom
的操作来设置iframe
,最重要的方面是它还用了定时器每隔一小段时间就检测,然后就触发函数,判断是否改变等等。插入一个空的iframe
(经过属性设置)的作用在这里是存储hash
的值和存储hash
改变记录。在这里我遇到了一个问题:存储hash
的值完全可以通过一个全局变量来完成,为什么要大费周折创建一个iframe
呢?下面是个人的一些猜测:
Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
这是源码中的一句注释。用iframe
的理由可能是为了通过开关iframe
来存储记录。说实话具体是什么原理还不清楚,如果有人了解的话欢迎指教~
// 开关`iframe`
iWindow.document.open();
iWindow.document.close();
2.3 实现(start, navigate & 事件)
2.3.1 start
start
主要做如下操作:
进入
start
函数之后会把started
设置为true
防止重复出发。设置各种参数,用于后期判断使用哪一种路由控制方式。
如果有
hashchange
事件,但没有pushState
方法,就用location.replace
方法来改变路由。如果两者都有就调用navigate
函数,里面可以通过pushState
改变并记录路由。如果两者都没有就设置iframe
并启动,通过设置iframe
的hash
参数来改变路由。绑定事件,用
hashChange
方法的绑定hashchange
事件,用pushState
方法的绑定popstate
事件,用iframe
的使用setInterval
来监听。
2.3.2 navigate
进入函数之后首先是进行“组合”,“组合”出url
。这个过程需要有root
和fragment
,后者需要调用getFragment
函数,前者需要根据是path
还是hash
来对root
进行处理。如果是hash
就不需要加/
,如果是path
就要加/
。解码后判断当前的this.fragment
和有没有发生变化,没有不管,有就更新。
根据浏览器使用不同的方法。注意这里使用的判断的依据是在start
函数里面就定义好的。
如果有
pushState
或者replaceState
就用;如果有
hashchange
就仅仅只调用_upadateHash
,传入当前的location
bom
对象,里面用了location.replace
,更新当前的href
或者hash
;-
如果没有
hashchange
就需要把当前location
和iframe.location
对象分别传入_updateHash
,然后更新当前href
或者hash
。还有一个需要注意的是是否
replace
,这是一个传入参数,判断时候要影响history
。
2.3.3 事件
关于路由触发事件是通过两个函数来完成的,它们分别是checkUrl
和loadUrl
, 前者会检测路由是否发生了改变,如果改变了就会触发navigate
函数并调用loadUrl
函数,而后者会通过路由片段来找到handlers
相关的事件函数来触发。这就实现了用户在routes
对象里面设置的事件了。
3. Sync
最后来个简单的Sync
的讲解吧~有关ajax
的部分在backbone
中其实是通过Backbone.ajax
函数来代理jquery
或者其他可以发起ajax
的库的。而Sync
函数事实上主要的工作就是部署ajax
参数,最后调用这个Backbone.ajax
发起请求。通过源码可以看到,其中为params
设置了type
, dataType
, url
, contentType
, data
, (processData
)属性来作为发起ajax
的参数。其中也为options
设置了beforeSend
, error
方法作为ajax
的回调(success
函数写在其他模块中,详情可以看我之前的几篇文章)。
其中还需要主要的有两个参数emulateJSON
& emulateHTTP
。在文档中的介绍非常详细,个人觉得在大部分时候都不会用到。
4. 最后的话
终于把最后的第三篇文章写出来了...花了很长时间...还是觉得如果要真正完全读懂backbone
源码要多读代码(惭愧,自认为还没完全达到),多查资料,多读一些源码解析。有关于router
和history
我觉得这篇文章还是很棒的。这一个部分个人感觉确实不是很好懂,但是可以学习到很多有关路由的处理的相关知识,其实是非常有益的~
backbone
被称为框架的框架。这个框架的思想比起使用更有意义,毕竟现在有更多功能强大的框架。新东西要学,但是经典也是不应该被落下的。
如果这篇文章有什么错误的地方请轻喷~互相学习!谢谢大家。
下面是全部的文章:
基于 Backbone + node 的个人简历生成器(个人学习总结)
Backbone源码解读(一)
Backbone源码解读(二)
Backbone源码解读(三)