注意:强烈建议一边阅读源码一边阅读本文。

终于到了backbone源码解读的最后一篇,这一篇和前面几篇时间上有一定的间隔(因为要回学校有一堆乱七八糟的事...)。在这一篇里面会讲解Bakcbonesync & router & histrorysync比较简单,但是路由的部分就比较复杂了。个人觉得是整个backbone源码里面最不好懂的一个部分,这个部分也使得backbone可以方便实现可以“返回”的单页面应用。个人觉得这个部分其实并没有很MVC有很密切的关系,但是它非常重要。读过源码就会发现,其实这一个部分与其他的模块(ModelCollection & View)相对独立。如果不希望使用backbone但希望能用到这个路由系统的话估计拆出来难度也不会很大。

总体来说路由模块由RouterHistrory组成,而Router事实上可以看成是对History的一个封装。用户直接操作的部分时Router,相关函数的处理(路由匹配时调用的函数)也是在Router中完成。History主要是处理一个更加棘手的问题,就是有关链接的问题。这里面有关于跨浏览器的解决方案是非常值得学习的。

1. Router

Router是对History的封装,也是给用户定义路由的接口。一般来说,用户在使用Router的时候会定义一个routers的对象,里面是想关路由与处理函数组成的key-valueRouter代码中主要做两件事,一件是对正则表达式的操作(可以看见里面很多令人痛苦的正则表达式),另一件事就是事件相关的绑定了。Router的相关代码不多,写得比较精简。

1.1 函数绑定与执行

Router里面定义了两种方法来定义路由事件,一个方法是用户定义routers,通过初始化调用_bindRoutes函数;一个方法是用自带的route函数。其实两个方法本质上都是调用了route函数_bindRoutes里面实际上也是通过循环来调用route函数。因此路由函数重点在于route函数。

route函数里面,一开始是进行一些参数的整理。然后就是调用了History模块的route函数,把正则(匹配参数用的)和一个回调函数传了进去。在回调函数里面就是做 执行函数——触发router的事件——触发history的事件这几个步骤,在Historyroute函数里面,只是简单的插入地把keycallback组成对象插入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方式。使用historypushState方法修改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并启动,通过设置iframehash参数来改变路由。

  • 绑定事件,用hashChange方法的绑定hashchange事件,用pushState方法的绑定popstate事件,用iframe的使用setInterval来监听。

2.3.2 navigate

进入函数之后首先是进行“组合”,“组合”出url。这个过程需要有rootfragment,后者需要调用getFragment函数,前者需要根据是path还是hash来对root进行处理。如果是hash就不需要加/,如果是path就要加/。解码后判断当前的this.fragment和有没有发生变化,没有不管,有就更新。

根据浏览器使用不同的方法。注意这里使用的判断的依据是在start函数里面就定义好的。

  • 如果有pushState或者replaceState就用;

  • 如果有hashchange就仅仅只调用_upadateHash,传入当前的location bom对象,里面用了location.replace,更新当前的href或者hash

  • 如果没有hashchange就需要把当前locationiframe.location对象分别传入_updateHash,然后更新当前href或者hash

    还有一个需要注意的是是否replace,这是一个传入参数,判断时候要影响history

2.3.3 事件

关于路由触发事件是通过两个函数来完成的,它们分别是checkUrlloadUrl, 前者会检测路由是否发生了改变,如果改变了就会触发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源码要多读代码(惭愧,自认为还没完全达到),多查资料,多读一些源码解析。有关于routerhistory我觉得这篇文章还是很棒的。这一个部分个人感觉确实不是很好懂,但是可以学习到很多有关路由的处理的相关知识,其实是非常有益的~

backbone被称为框架的框架。这个框架的思想比起使用更有意义,毕竟现在有更多功能强大的框架。新东西要学,但是经典也是不应该被落下的。

如果这篇文章有什么错误的地方请轻喷~互相学习!谢谢大家。

下面是全部的文章:
基于 Backbone + node 的个人简历生成器(个人学习总结)
Backbone源码解读(一)
Backbone源码解读(二)
Backbone源码解读(三)


harryfyodor
406 声望32 粉丝

关注前端,关注JS, React, Vue, Nodejs, Backbone.