一. Backbone的江湖地位:

backbone作为一个老牌js框架为大规模前端开发提供了新的开发思路:前端MVC模式,这个模式也是前端开发演变过程中的一个重要里程碑,也为MVVM和Redux等开发思路奠定了基础,后来的react,vue无不是在backbone的影响下开创出来的经典模式。为什么这么说呢?我们先来回顾下Web前端开发的大概演变流程,本过程纯粹个人理解,抛砖引玉,共同探讨,如有偏差请看官指出错误:

1. 无前端:最早的网页就是HTML,还只是静态页面。世界上第一个网页:

 

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只是UI层面的框架,并没有包含逻辑层面,另外两个问题还需要解决:代码组织结构和组件通信。MVC模式也是为了解决前端复杂度问题,但是仍然不是最佳方案,试想以下的情景:

  

通过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) 需求变化时,由于维护成本居高不下,更新需求的开发效率底下。

e) 单页面开发比较繁琐

而MVC模式从一定程度上改观了上述问题。

(3)当你的项目中很多页面属于单页面模式,并且有很多DOM操作,数据的查询,修改等等,你可以使用backbone更好的组织代码,提高开发效率,降低维护成本。

2. 他有什么优点?

(1)View可以划分UI,UI的复用性更高,增加UI的内聚度,降低耦合

(2)View和Model事件机制进行通信,组件更加独立

(3)MVC架构让代码结构清晰可维护

(4)CRUD 比ajax请求更加便捷

(5)单页面开发更加方便

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的映射,实现后台和前进按钮的单页效果,Router路由模块的主要作用就是记录用户定义的路由规则,作为History的对外接口。例如:

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:如果设置hashChange选项为true并且浏览器支持,则开启该模式,该模式通过修改url的hash值并且监控hashChange事件来实现单页面效果,可以实现无刷新更新,但是url是通过hash来区分的,体验略差

c) url跳转:如果浏览器既不支持pushState也不支持hashChange事件,backbone只好使用了url跳转的方式来实现更新,创建一个隐藏的iframe来记录上次的url的hash,创建一个定时器来定时比较两者hash是否更新,体验不太好

所有这些变化都仅仅是体现在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: 执行单页面的历史记录管理,如果是pushState模式,则调用replaceState或pushState来管理历史,如果hasChange模式,则通过更新url的hash管理历史,否则直接跳转url,通过浏览器自己管理历史

 

7. Sync异步请求:ajax请求模块,主要是对ajax的配置,最终调用的是Backbone.ajax方法,如果有jQuery调用jQuery的ajax方法,可以被改写。

 

8. extend扩展函数:返回一个新的对象child,将实例对象和静态对象拷贝到目标对象中去,通过寄生组合继承方式将child继承自实例对象,通过understore库的extend方法将静态对象拷贝到child中去,然后返回child对象。

 

posted on 2017-06-03 01:16  zhusheng  阅读(345)  评论(0编辑  收藏  举报