背景
前几天在前端群里面闲聊时候,有好多年的老同行感叹说,08年出来的那帮人那时候很少用框架,jQuery在那时候还没有多少人用,不像现在有N多框架可以选,现在的新人着实幸福。我回答说,08年其实也是幸福的时代,可以专心研究javascript基础,而不像现在这样,被漫天的框架潮流拖着走。群友总结说,跟着时代的走的好处是,后来者,要重走一次历史线路,是一件很艰难的事情。
那么,此刻,刻意来学backbone和其中思想,便应当是后来人对历史的其中一次回首,目标真的是进一步提升对MVC的理解.
《论前端工程师的修养》里面说,前端的价值,20%是知识,这里包含诸多框架经验,80%则是能力,这能力包括编程能力、架构能力和工程能力。想想觉得总结得真的很好。
不在乎前进的路难,而在乎浮云遮眼不知何将往。
声明
文章写之前,我必须声明一次,这不是一番深思熟虑改之又改的文章,而仅仅是一个学习笔记,我想将backbone整理过程中,一路的想法细细展示出来。聊做以后再回首的记忆。
大抵最美好的青春岁月,都给编程了,留个印记也好。。。
预计的学习线路
目标是先浏览一次API,然后熟悉API,做一些小项目,然后开始根据API来对源码进行原理猜测,最后进行源码架构整理阅读进行印证。
简单说的话,就是一句,看api,实践一下,然后猜测实现细节,然后看源码。
API熟悉环节
先找个中文的API看,这样进度会快点。文档是1.12的翻译,我认为不是太落后当前其实也够学习用了。
先整理一下Backbone的API大类,以一直以来源码阅读经验来看,从宏观向微观读,理解架构再理解细节才是唯一的了解和阅读源码的路径:
- Backbone.Events
- Backbone.Model
- Backbone.Collection
- Backbone.Router
- Backbone.history
- Backbone.sync
- Backbone.View
- Utility
- Backbone.noConflict
- Backbone.$
简单的看了下这几部分的构成,从命名来看,很显然里面有事件、模型、集合,路由、历史、同步、视图、辅助工具这几个大类的东西,其他细节应该都是依附在这几个对象下作为方法使用。
这里猜测一下可能的库的延伸发展路径:
- Backbone作为以MVC(Model-View-Collection)主导的思想的框架,最主要的,那么也只可能是MVC三个角色了,Model-View-Collection这三个对象,应当就是BackBone编写开始最先写下三个的词汇。
- 现代网页程序对交互的处理必须依托事件来触发,所以需要一个Events来对时间进行统筹处理,API的说明是,一些事件对Model也有作用,那么这里面应该有一个订阅-发布模式的处理方案。
- Router是路由处理,这个应当是考虑到当前页面复杂的结构设计,所以产生的一个模块,对路由统筹处理,以达到归并路由逻辑,防止到处横生节蔓的方案。
- history这个应当是html5的hashchange事件的封装,依托hashchange事件,来处理单页面路由刷新页面后导致状态丢失和无法前进后退的处理方案。
- sync这个模块应当是所有前端数据同后端服务器进行ajax交互的统一封装。
- 关于Model和Collection的关系,猜测应当是:Model(对象数据,也可能被封装)既是Collection(数组数据,也可能被封装)中的一个个对象,也是内部每个对象的整体数据结构骨架,所有Model都保持统一的结构,以便实现ORM。
- Utility这个貌似不用说了,大家都知道。
API细节整理
这里简单整理一下,不过也可能变成一份简略版本的API…
不过这里一切整理从简
Event
Event的方法和简单整理
1 2 3 4 5 6 7 8 9
| Events on 语法:object.on(event, callback, [context]) off 语法:object.off([event], [callback], [context]) trigger 语法:object.trigger(event, [*args]) once 语法:object.once(event, callback, [context]) listenTo 语法:object.listenTo(other, event, callback) stopListening 语法:object.stopListening([other], [event], [callback]) listenToOnce 语法:object.listenToOnce(other, event, callback)
|
事件这里定义的方法不多简单整理:
- on对off一个绑定一个解绑,listenTo对stopListening一个订阅一个取消订阅(猜测!)
- once对应on,一个是触发一次,一个是一直触发
- listenToOnce和listenTo对应类似once对应on,一个订阅一次,一个订阅N次。
Event定义的事件:
- “add” (model, collection, options) — 当一个model(模型)被添加到一个collection(集合)时触发。
- “remove” (model, collection, options) — 当一个model(模型)从一个collection(集合)中被删除时触发。
- “reset” (collection, options) — 当该collection(集合)的全部内容已被替换时触发。
- “sort” (collection, options) — 当该collection(集合)已被重新排序时触发。
- “change” (model, options) — 当一个model(模型)的属性改变时触发。
- “change:[attribute]” (model, value, options) — 当一个model(模型)的某个特定属性被更新时触发。
- “destroy” (model, collection, options) —当一个model(模型)被destroyed(销毁)时触发。
- “request” (model_or_collection, xhr, options) — 当一个model(模型)或collection(集合)开始发送请求到服务器时触发。
- “sync” (model_or_collection, resp, options) — 当一个model(模型)或collection(集合)成功同步到服务器时触发。
- “error” (model_or_collection, resp, options) — 当一个model(模型)或collection(集合)的请求远程服务器失败时触发。
- “invalid” (model, error, options) — 当model(模型)在客户端 validation(验证)失败时触发。
- “route:[name]” (params) — 当一个特定route(路由)相匹配时通过路由器触发。
- “route” (route, params) — 当任何一个route(路由)相匹配时通过路由器触发。
- “route” (router, route, params) — 当任何一个route(路由)相匹配时通过history(历史记录)触发。
- “all” — 所有事件发生都能触发这个特别的事件,第一个参数是触发事件的名称。
Model 模型和单个数据操作
写到这里,不禁仰天长叹:Model的方法真的很多啊啊啊啊…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| Model extend 语法:Backbone.Model.extend(properties, [classProperties])用途不说了,应该同jquery的extend constructor/initialize 语法:new Model([attributes], [options]) ,用来创建初始属性 get 语法:model.get(attribute),获取属性值 set 语法:model.set(attributes, [options]) 设置属性值 escape 语法:model.escape(attribute),转义版get has 语法:model.has(attribute), 属性值为非 null 或非 undefined 时返回 true。 unset 语法: model.unset(attribute, [options]) 从内部属性散列表中删除指定属性(attribute)。 clear 语法: model.clear([options]) 从model中删除所有属性, 包括id属性。 id 语法: model.id id是model的特殊属性,可以是任意字符串 idAttribute 语法: model.idAttribute 一个model的唯一标示符,被储存在 id 属性下 cid 语法: model.cid model的特殊属性,cid 或客户 id 是当所有model创建时自动产生的唯一标识符。 attributes 语法: model.attributes attributes 属性是包含模型状态的内部散列表 changed 语法: model.changed changed属性是一个包含所有属性的内部散列,自最后 set 已改变。 defaults 语法: model.defaults or 语法: model.defaults() defaults 散列(或函数)用于为模型指定默认属性。 toJSON 语法: model.toJSON([options]) 返回一个模型的 attributes 浅拷贝副本的 JSON 字符串化形式。 sync 语法: model.sync(method, model, [options]) 使用 Backbone.sync 可以将一个模型的状态持续发送到服务器。 fetch 语法: model.fetch([options]) 通过委托给Backbone.sync从服务器重置模型的状态。 save 语法: model.save([attributes], [options]) 通过委托给Backbone.sync,保存模型到数据库 destroy 语法: model.destroy([options]) 通过委托给Backbone.sync,保存模型到数据库 validate 语法: model.validate(attributes, options) 自定义验证逻辑 validationError 语法: model.validationError 用validate最后验证失败时返回的值。 isValid 语法: model.isValid() 运行validate来检查模型状态。 url 语法: model.url() 返回模型资源在服务器上位置的相对 URL urlRoot 语法: model.urlRoot or 语法: model.urlRoot() 设置生成基于模型 id 的 URLs 的默认 url 函数 parse 语法: model.parse(response, options) parse 会在通过 fetch 从服务器返回模型数据,以及 save 时执行 clone 语法: model.clone() 返回该模型的具有相同属性的新实例。 isNew 语法: model.isNew() 模型是否已经保存到服务器。 hasChanged 语法: model.hasChanged([attribute]) 标识模型从上次 set 事件发生后是否改变过。 changedAttributes 语法: model.changedAttributes([attributes]) 只从最后一次set开始检索已改变的模型属性散列 previous 语法: model.previous(attribute) 在 "change" 事件发生的过程中,本方法可被用于获取已改变属性的旧值 previousAttributes 语法: model.previousAttributes() 返回模型的上一个属性的副本。
|
姑且开始尝试分类一下:
- 数据持久化:sync,save,fetch,destroy
- 数据读写相关:get,set,escape,unset,clear,toJSON,validate,validationError,isValid,parse,clone
- Model属性相关:id,cid,attributes,changed,defaults,url,urlRoot,isNew,changedAttributes,previous,previousAttributes
大概猜测下这些玩意儿的用途和场景:
- 数据持久化:
- sync 数据双向同步
- save 本地数据同步到服务器
- fetch 获取服务器数据
- destroy 销毁指定数据
- 数据读写相关:
- get 获取数据
- set 设置数据
- escape 转义get的值
- unset 删除单个属性
- clear 清除整个单个条目
- toJSON 数据转换为json
- validate 验证
- validationError
- isValid 判断验证通过否
- parse 序列化json数据
- clone 深拷贝数据
- Model属性相关:这个猜不到先放放
Collection 集合操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Collection extend 语法Backbone.Collection.extend(properties, [classProperties]) model 语法:collection.model constructor / initialize models 语法:collection.models toJSON 语法:collection.toJSON([options]) sync 语法:collection.sync(method, 语法:collection, [options]) add 语法:collection.add(models, [options]) remove 语法:collection.remove(models, [options]) reset 语法:collection.reset([models], [options]) set 语法:collection.set(models, [options]) get 语法:collection.get(id) at 语法:collection.at(index) push 语法:collection.push(model, [options]) pop 语法:collection.pop([options]) unshift 语法:collection.unshift(model, [options]) shift 语法:collection.shift([options]) slice 语法:collection.slice(begin, end) length 语法:collection.length comparator 语法:collection.comparator sort 语法:collection.sort([options]) pluck 语法:collection.pluck(attribute) where 语法:collection.where(attributes) findWhere 语法:collection.findWhere(attributes) url 语法:collection.url or 语法:collection.url() parse 语法:collection.parse(response, options) clone 语法:collection.clone() fetch 语法:collection.fetch([options]) create 语法:collection.create(attributes, [options])
|
尝试分类一下:
- 持久化 sync,fetch
- Collection对象读写相关:extend,model,models,remove,reset,get,set,findWhere,create,toJSON
- 集合(数组)操作:push,pop,unshift,shift,slice,sort,comparator,pluck,at
貌似差不多了,API里面方法最多了都整理完了,送了一口气,我继续整理。。。
Router
1 2 3 4 5 6 7
| Router extend 语法:Backbone.Router.extend(properties, [classProperties]) routes 语法:router.routes constructor / initialize 语法:new Router([options]) route 语法:router.route(route, name, [callback]) navigate 语法:outer.navigate(fragment, [options]) execute 语法:executerouter.execute(callback, args)
|
history
1 2
| history start 语法:Backbone.history.start([options]) 启动hashchange事件监控
|
sync
1 2 3 4 5 6 7 8
| sync Backbone.sync 语法:sync(method, model, [options]) - method – CRUD 方法 ("create", "read", "update", or "delete") - model – 要被保存的模型(或要被读取的集合) - options – 成功和失败的回调函数,以及所有 jQuery 请求支持的选项 Backbone.ajax 语法:Backbone.ajax = function(request) { ... }; Backbone.emulateHTTP 语法:属性值 设置为true可以用POST模拟put等http方法 Backbone.emulateJSON 语法:属性值 设置为true序列化JSON为formData
|
View
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| View extend 语法:Backbone.View.extend(properties, [classProperties]) 开始创建自定义的视图类。 constructor / initialize el 语法:属性值,设置view的元素 $el 语法: 属性值,一个视图元素的缓存jQuery对象。 setElement 语法:view.setElement(element) 如果你想应用一个Backbone视图到不同的DOM元素 attributes 语法:属性 接受对象 或者返回对象的函数 属性的键值对,将被设置为视图el上的HTML DOM元素的属性 $ (jQuery) template 语法:view.template([data]) render 语法:view.render() 重载本函数可以实现从模型数据渲染视图模板 remove 语法:view.remove() 从 DOM 中移除一个视图,并解绑所有事件 delegateEvents 语法:delegateEvents([events]) 事件委托 很多时候不需要手动运行 undelegateEvents 语法:undelegateEvents() 上例的相反的操作
|
来一个Demo上的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| var DocumentView = Backbone.View.extend({ events: { "dblclick" : "open", "click .icon.doc" : "select", "contextmenu .icon.doc" : "showMenu", "click .show_notes" : "toggleNotes", "click .title .lock" : "editAccessLevel", "mouseover .title .date" : "showTooltip" },
render: function() { this.$el.html(this.template(this.model.attributes)); return this; },
open: function() { window.open(this.model.get("viewer_url")); },
select: function() { this.model.set({selected: true}); },
...
});
|
集合例子来看,View的操作远比想象的要简单:
- 设定模板
- 渲染模板到指定el
- 设定委托事件
就这么三步了。
总结
大致结构我猜测就是这样了,先来个简单的总结:
上图: