一. Backbone的江湖地位:
backbone作为一个老牌js框架为大规模前端开发提供了新的开发思路:前端MVC模式,这个模式也是前端开发演变过程中的一个重要里程碑,也为MVVM和Redux等开发思路奠定了夯实的基础,后来的react,vue无不是在backbone的影响下开创出来的经典模式。为什么这么说呢?我们先来回顾下Web前端开发的大概演变流程,本过程纯粹个人理解,抛砖引玉,共同探讨,如有偏差请看官指出错误:
1. 无前端:最早的网页就是HTML,还只是静态页面,当时的脚本含量极少甚至没有js,js只是作为一个辅助工具而已。
2. 萌芽:由于用户体验问题(比如文本校验等等),开发人员把服务端脚本(比如python,perl)等语言搬到到浏览器端,也就是JavaScript,让浏览器可以分担一部分任务,而不需要请求后台服务器,这在当时网络只有几十KB的环境下,用户体验效果提升非常明显。
3. 混沌:这个时候各个网站为了提升用户体验,丰富网站能力,开始了各种各样的尝试,ASP,PHP,JSP等等各种模式迅速成长起来,服务器脚本和JS脚本共同协作把数据展现在页面中,处理用户交互请求,页面效果和能力得到大大提升,互联网在这个时候开始突飞猛进的发展起来。然而此时的架构只是单单解决了功能需求,却没有考虑系统的维护性,扩展性和性能,安全等各个方面,前后端尚未分离,还处于混沌阶段。那个时候还没有Web前端开发岗位,只有PHP,ASP或者JSP开发人员。
4. 后端MVC:由于项目的日渐扩大,系统的复杂度也日渐加深,Web程序愈发臃肿,维护性越来越成问题,于是出现了后端MVC模式,比如Structs、Spring MVC,ASP.NET等等,这种模式把服务端逻辑管理起来,以Model,View,Controller的模式来开发,将数据,视图和操作逻辑分离开来,大大提高了代码的清晰度和可维护性,让之前的混沌局面有了很好的改观。然而此时此刻前后端仍没有分离,HTML,JS,后台脚本语言(PHP,JSP,ASP,ASP.NET)等等仍然混杂在一起,当需求频繁变化时,视图和逻辑修改仍然不能完全分离。
5. Ajax:后来就是出现了大名鼎鼎的ajax以及各种对应框架,让前后端得到了分离,前端处理UI展现,用户交互,后台处理数据查询,更新,后台逻辑计算等等。从此Web前端开发诞生,部分开发人员开始专注于Web前端领域,开始了历史上的分久必合合久必分的"分"。此时很多逻辑被划分到浏览器端,前端js的代码规模开始日渐复杂起来,这时候出现很多经典框架比如大名鼎鼎的jQuery,其高速处理DOM,屏蔽浏览器兼容性,事件封装,ajax封装等功能确实非常好用。
6. 模块化:随着js代码越来越复杂,规模越来越大,扩展性和维护性又出现在开发人员面前,同时由于js语言的特性,没有模块或者说包的概念,导致js语言容易引起变量冲突,脚本依赖冲突等等,代码复用率低等等问题,为了解决这些问题,Web前端进入到了模块化开发时代,这个时代英雄辈出,包括YUI,AMD,CMD等等(requireJs,seaJs),在这个阶段js代码被模块化起来,代码的维护性,复用度得到了极大的提升。
7. 前端MVC:模块化开发虽然解决了纯逻辑模块的代码维护性问题,但是Web开发毕竟是处理用户界面操作,不可避免的要处理各种UI视图问题,而js必须要和HTML,数据进行紧密的配合,而这些代码依然是混杂状态,最常见的场景就是js去操作各种DOM,HTML模版等等,代码的耦合度很高,维护性很低。自此BackboneJS、EmberJS、KnockoutJS、AngularJS的出现,让这些问题有了改观,通过将后端MVC模式引入到前端,将前端逻辑也划分为Model,View,Controller等模式,比如今天的主角Backbone,他定义了Model,Collection,View等模式,把代码的逻辑规范起来,有了一定的套路,极大的提高了开发效率,降低了代码耦合性,这里特别说明一下,由于Web开发的特殊性,Contoller是无法完全从HTML分离开来的,所以在Backbone中,没有独立的Contller模块,而是在Model和View,还有Router中体现出来,Model,View和Router都可以发起Controller逻辑操作。在这个阶段,前端大规模开发终于有了自己的套路。
8. MVVM:MVVM是MVC在View层的一个优化,由于MVC开发过程中,View层会出现很多DOM的查询,操作等等,非常繁琐复杂,DOM性能也出现瓶颈,同时由于HTML标签的局限性,UI层面的开发比较繁琐,为此Vue和React提出了MVVM的模式,在这个模式下开发人员彻底抛弃DOM,以数据和View的绑定式开发,大大减少了Web开发中DOM操作的代码量,同时提出了虚拟DOM的模式,一定程度上也提升了DOM操作的性能,其次,提出了全组件式开发,丰富了HTML标签的能力,通过创建组件的方式,创建了新的HTML标签,以组件的方式来开发Web页面,从而降低了页面代码的耦合度,提升了Web开发的效率。
9. Redux/Vuex:在大规模Web开发过程中,仅有MVVM是不够的,MVVM只是对DOM进行了抽象,但是另外两个问题还需要解决:代码组织结构和组件通信。MVC模式也是为了解决前端复杂度问题,但是仍然不是最佳方案,试想以下的情景:
VS
通过Controller来驱动Model和View的逻辑,当Model和View有成千上百的规模的时候,你会发现,这里的逻辑觥筹交错,有时候并不能很明确的知道到底是哪里改变了当前的视图,同时当一个数据发生变化以后,又会有多少个View会发生变化,完全不可预测,甚至失控。为此开发人员借用了Flux的开发思路,提出了Redux的开发模式,他提出三大原则,在这个模式下,数据单向流动对UI产生影响,让开发人员可以清晰的看到数据的流向,开发人员只对数据进行操作,View由数据发生变化而刷新,从而使整个逻辑的流向非常清晰可追溯,可控。
10. Node全栈时代:"话说天下大势,分久必合,合久必分",随着前后端分离多年之后,Node的出现让全栈开发又重新回到了前端开发的视野,Web开发人员不甘于仅限在浏览器端折腾,V8的出现让开发人员可以通过js语言在服务器端开发,至此,是不是说Node又要一匡天下了呢?
二. Backbone介绍
好了废话不多说,到这里你应该知道BackboneJs在历史中的来历和地位了吧?虽然Backbone有着自己的优缺点,而且现在已经不再是Backbone风光的时代了,但是了解Backbone的设计思想和源码结构,还是对我们日常开发还是非常有帮助的。我们首先看几个问题
1. backbone是什么?backbone带来了什么?为什么要使用backbone?
(1)首先backbone是一个前端MVC框架,为大型Web程序提高了一个骨架的作用,让开发人员更好的组织和设计代码,他需要跟understore和jQuery一起配合。
(2)backbone带来了MVC模式,分离了UI和数据,带来了事件通信机制,带来了单页面开发的便捷方式。没有MVC之前的粗犷模式有哪些问题:
a) 数据保存在内部变量中和业务模型无关,杂乱零散不可控
b) 代码复用率低,模块化后有些改观,但无法从业务层面复用
c) 代码结构耦合在一起,不清晰,维护成本高
d) 需求变化时,由于维护成本居高不下,更新需求的开发效率底下。
而MVC模式从一定程度上改观了上述问题。
(3)当你的项目中很多页面属于单页面模式,并且有很多DOM操作,数据的查询,修改等等,你可以使用backbone更好的组织代码,提高开发效率,降低维护成本。
2. 他有什么优点?
(1)View可以划分UI,UI的复用性更高,增加UI的内聚度,降低耦合
(2)View和Model事件机制进行通信,组件更加独立
(3)MVC架构让代码结构清晰可维护
(4)CRUD 比ajax请求更加便捷
3. 他有什么缺点?
(1)Model设计比较简单,无法满足复杂数据关系,如多对多的数据关系等等
(2)写代码需要小心,避免内存泄漏问题
4. 设计思路是什么?
Backbone的设计思路,就是把UI分为View来定义,数据分为Model来定义,多个Model可以保存在Collection,在Model,Colletion和Router中实现CURD操作,通过事件驱动来更新View,主要有3种事件驱动
(1) 浏览器DOM事件:绑定在DOM中的浏览器事件
(2) 模型事件:Model和Collection都有save,change等事件
(3) 路由事件:路由变化事件
最终的效果就是用MVC模式来管理组织代码。
我们可以看到上面的流向和React和Vue相比其实还是有些杂乱的,但在当时和jQuery杂乱绑定DOM的相比已经是进步很多了。
三. backbone源码解析
Backbone源码其实非常简单,直接比对源码即可。简单说明下,总共有8大模块,每个模块都设计的非常规范:
1. Event事件模块:一个事件处理模块
(1) on:注册事件回调函数,如果传人是json对象或空格分隔的多个事件,通过eventsApi方法遍历处理
(2) once:注册只执行一次的回调函数
(3) off:删除事件回调函数,如果没有指明事件名,删除所有事件的回调列表,如果没有指明回调函数,删除该事件的所有回调函数,如果都有指定则删除指定的事件回调函数。如果传人是json对象或空格分隔的多个事件,通过eventsApi方法遍历处理
(4) trigger:触发指定的事件,执行该事件的回调函数列表,如果该对象有all事件,则也会触发all事件。如果传人是json对象或空格分隔的多个事件,通过eventsApi方法遍历处理
(5) listenTo:把事件和回调对象绑定到指定的对象上去。
(6) listenToOnce:同listenTo,只是只会执行一次
(7) stopListening:将对象的事件对象off掉
(8) eventsApi:如果传入的事件是json对象或空格分隔的多个事件,遍历json对象或多个事件,挨个执行指定的操作。
2. Model模型:数据模型,包括数据的设置,获取,后台服务的增删查改等操作
属性:
(1) cid: 当前数据模型的唯一ID,由underscore库生存
(2) attributes:当前数据模型的属性
(3) collection:数据模型所属的集合对象
(4) changed:保存了属性值发生变化的对象集合
方法:
(1) 构造函数: 创建cid,设置collection,如果有parse方法就做parse转换,最后把属性参数设置到本数据模型中,调用initialize方法。
(2) initialize:一个空函数,如果你需要在初始化进行操作可以重写这个方法。
(3) get:或者某个属性的值
(4) set:设置属性值,并将值发生变化了的属性保存到changed对象中去
(5) has:判断数据模型是否包含某个属性
(6) clear:清空数据模型的属性
(7) fetch:通过ajax请求获取模型的属性值
(8) save:保存模型值到后台,如果设置了wait参数,必须要后台返回成功才能更新本地数据
(9) destory:删除数据模型值,如果设置了wait参数,必须要后台返回成功才能更新本地数据
最后将understore库的‘keys‘, ‘values‘, ‘pairs‘, ‘invert‘, ‘pick‘, ‘omit‘, ‘chain‘, ‘isEmpty‘方法添加到Model对象中。
3. Collection集合:Model对象的集合,包括对数据模型的添加,删除,设置,增删查改等操作
属性:
(1) model:Mode对象
(2) models:Model实例集合数组
方法:
(1) 构造函数: 初始化参数,设置model对象,初始化models数组,调用initialize方法,如果传入了model对象数组,清空原数组的依赖关系,将原数组保存到option中去,将新的model数组添加到models数组中去。
(2) initialize:一个空函数,如果你需要在初始化进行操作可以重写这个方法。
(3) add:添加一个Model对象到本集合中去(也就是models数组)
(4) remove:删除一个或多个Model对象,清理_byId对象,清理依赖关系等等
(5) set/get:get获取指定的model对象,set添加model对象到models数组中去,添加新的对象,合并已经存在的对象,删除未列出的对象,
(6) push/pop:添加或删除model对象到models数组尾部
(7) shift/unshift:添加或删除model对象到models数组头部
(8) where:查找指定属性的model对象集合
(9) fetch:从后台读取Model数据
(10) create:创建一个新的model数据,并保存到后台中去
最后将understore库中的所有数组的方法和排序方法添加到Collection对象中去。
4. View模型: 视图模块,对UI中的DOM元素进行操作,并注册浏览器事件
属性:
(1) cid:视图id
(2) el:视图所在的DOM对象
(3)$el:视图所在DOM对象的jQuery对象
(4)id:视图所在DOM对象ID属性
(5)className:视图所在DOM对象className
(6)events:绑定到该视图的对象列表
方法:
(1) 构造函数: 设置cid,初始化option,调用_ensureElement和initialize,其中_ensureElement的作用是确保el是存在的,如果不存在创建一个DOM对象,然后将该对象设置为el,jQuery对象设置为$el,删除el的所有原事件,绑定本视图的event对象列表,如果存在直接将该对象设置为el,后面的逻辑同上。
(2) initialize:一个空函数,如果你需要在初始化进行操作可以重写这个方法。
(3) render:视图渲染逻辑,需要用户重写
(4) remove:删除视图,将删除视图的DOM对象和事件逻辑
(5) setElement:将某个DOM对象设置为el,并绑定事件对象
(6) delegateEvents:为el添加事件对象
(7) undelegateEvents:为el删除事件对象
5. Router路由模块:主要定义和处理路由逻辑,配合History对象可以实现路由规则和url的映射,实现后台和前进按钮的单页效果。例如:
var myRouter = Backbone.Router.extend({ routes: { "help": "help", // #help "search/:query": "search", // #search/test "search/:query/p:page": "search" // #search/test/p2 }, help: function() { ... }, search: function(query, page) { ... } });
a) help对于help方法
b) search/:query传递一个参数
c) search/:query/p:page 传递两个参数,第二个参数略去p
d) "file/*path"匹配file/后的所有路径
e) "search/:query(/:page)"
可以匹配#serach/query
和 #serach/query/p1
,第一种情况,传入 "query"
到路由对应的动作中去, 第二种情况,传入"query"
和 "p1"
到路由对应的动作中去
属性:
(1) routes:路由列表
方法:
(1) 构造函数: 初始化路由列表,调用_bindRoutes和initialize。
(2) initialize:一个空函数,如果你需要在初始化进行操作可以重写这个方法。
(3) _bindRoutes:将路由列表绑定到实例中去
(4) route:绑定单个路由,首先判断是否是正则表达式,不是的化调用_routeToRegExp转换,首先添加到history对象的handler数组中,然后添加回调函数,回调函数中回调callback,并分析路径中的参数传入到callback中。
(5) execute:执行某个路由的回调,每当路由和其相应的callback匹配时被执行,可以被改写自定义包装或解析路由
(6) navigate:调用history对象的navigate方法,将路由保存到history中去,或者设置trigger:true跳转url,设置replace:true则只跳转,不保存历史。
(7) _routeToRegExp:将字符串转换为路由正则表达式
(8) _extractParameters:分析路径中的参数,比如search/:query/p:num中的query和p
6. History历史记录管理模块:配合路由进行历史记录管理,从而可以实现浏览器的后退前进操作,对于高级浏览器使用History API,对于旧浏览器实现兼容,Backbone实现了3种单页面效果,实现了优雅兼容:
a) pushState:如果设置选项pushState为true并且浏览器支持,则开启该模式,该模式的好处是,既能实现类似ajax方式的无刷新url的更新,也能实现url的变化,对用户来说交互体验最好
b) hasChange:如果设置hasChange选项为true并且浏览器支持,则开启该模式,该模式通过修改url的hash值并且监控hashChange事件来实现单页面效果,可以实现无刷新更新,但是url是通过hash来区分的,体验略差
c) url跳转:如果浏览器既不支持pushState也不支持hasChange,backbone只好使用了url跳转的方式来实现更新,创建一个隐藏的iframe来记录上次的url,创建一个定时器来定时比较两者来实现url的更新,体验不太好
所有这些变化都仅仅是体现在url和history历史记录中去,真正要更新页面的逻辑是路由定义的回调方法。
属性:
(1) handlers:路由规则列表
(2) location:window.location对象
(3) history:window.history对象
方法:
(1) 构造函数: 初始化路由列表,绑定checkUrl方法内部对象为History对象。
(2) atRoot:当前url是否为root根路径
(3) getSearch:获取参数部分
(4) getHash:获取hash部分
(5) getPath:获取路径和所有参数部分,如果option指定了root根路径,则在url中去掉该root
(6) getFragment:获取url中的片段,如果需要pushState或者不支持pushState的浏览器需要刷新url,则调用getPath,否则调用getHash
(7) start:开始监控路由,有两个选项:pushState模式通过pushState地址到history中去,监听popstate事件来实现单页面效果,如果是hasChange模式,通过更新hash和监听hashchange事件来实现,如果是老旧的浏览器,创建一个隐藏iframe记录上次的url,创建一个定时器定时比较当前url和iframe的url是否变化来觉得是否跳转。
(8) stop:停止监控popstate或hashchange方法,如果是iframe模式删除iframe,删除定时器
(9) route:将路由和回调函数添加到handler数组中去
(10) checkUrl:判断当前url是否发生了变化,如果是则调用loadUrl方法
(11) loadUrl:遍历路由列表,找到符合当前url的路由规则并执行回调
(12) navigate:
7. Sync异步请求:ajax请求模块,主要是对ajax的配置,最终调用的是Backbone.ajax方法,如果有jQuery调用jQuery的ajax方法,可以被改写。
8. extend扩展函数:返回一个新的对象child,将实例对象和静态对象拷贝到目标对象中去,通过寄生组合继承方式将child继承自实例对象,通过understore库的extend方法将静态对象拷贝到child中去,然后返回child对象。