缺乏架构气质的类库
如果想把 backbonejs 做为前端框架,还是算了,如果把 AngularJS 比作冲锋枪,那么 backbonejs 顶多是根树枝, 杀敌效率太低。 只有配合基于 backbonejs 的框架(例如,Marionette),才能提高生产力。
Models
哪些操作需要置于 Models 下呢?
- 数据的增、删、改、查
- 类型转换
- 权限控制
- 数据校验, 例如邮箱格式校验
- 生成展示所需的数据,例如,根据姓、名,生成全名
Backbone.js Model 内置的一些核心函数
- extend 继承 Backbone.Model,并在其上进行扩展
- set 设置属性的同时,会触发 change 事件。误区,我经常犯一个错误,就是用 model 事例加 .property 来读一个属性。。。总是忘用 get
- validate 校验数据,在 save() 以及 set() 设置了 {validate: true} 时会被调用
有个隐藏功能,即当 this.model 验证通过后,会返回设置成功的对象;若验证失败,会返回 false
var model = this.model.set(attrs, {validate: true});
if (!model) {
console.log('invalid attrs');
}
model.save 的结果处理,参考 How do I trigger the success callback on a model.save()?
this.model.save(null, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
});
参考
Views
介于 template 与 collection 之间,可以说是后台数据与前端展现间的胶水。
有点像傀儡戏中的控制者一般
哪些操作需要置于 Views 下呢?
- collection 的 add() 通常绑定了 Views 的 render()
- 用户点击、键盘操作对应的操作
- 创建一个新 DOM element 或者操作一个页面中已有的 element
Views.el 与 View.$el 的区别
- el 对应的是 html 代码
- $el 对应的是 jQuery object
避免 view render 的时候显示重复数据
render 之前,需要
this.$el.empty()
参考 Backbone.js collection view rendering duplicate items
sub view 带来的 memory leak
var AppView = Backbone.View.extend({
el: $("#core-element"),
showView: function(view){
if (this.currentView) {
this.currentView.unbind();
this.currentView.remove();
}
this.currentView = view;
this.currentView.render();
this.el.append(this.currentView.el);
},
});
var ItemRouter = Backbone.Router.extend({
formAddEditEl: $("#add-edit-form"),
routes: {
"": "index",
"/new": "add",
},
initialize: function(options){
this.collection = options.collection;
this.appView = options.appView;
},
index: function(){
var listView = new ListView({collection: this.collection});
this.appView.showView(listView);
},
add: function(){
var addEditView = new AddEditView({model: new Item()});
this.appView.showView(addEditView);
}
});
何时使用 tagName、el 创建 view, 两者有何区别
最理想的状态当然是使用 tagName,这样就不需要通过现有 element id 来绑定。 只需要在父 view 中 append 由 tagName 生成的 view,当 view 关联的 model 有 change 事件时,render 一下即可。
这种方式可以强制你细化 view。
Collections
即 Models 的集合
- 后台 RESTFul URL 的设置
- 增,删,改, 查
collection 发生变化的事件绑定放在哪里好?
采用排除法,逐一分析
- 放在 collection 的 initialize 中
在 collection 中指定特定的 view,但是如果指定了 view,那么这个 collection 就无法复用; 通过传参的方式传入 view,也不合理,如果一个页面上有联动同级 view,这种方式不可取。
- 放在 collection view 中
collection view 在实例化时,就需要传入一个 collection 的参数, 这个全局的 collection 实例可以定义在全局 app 对象中, 也可以定义在 app view 中。可行。
是否需要 app view?
如果不使用 app view,那么全局 app 对象需要做的事情是,建立 collection 对象,collection view 对象,拉取 collection。完全不需要用到 app view。
add 与 create 的区别
create 会触发调用 add。create 的实际流程是
- 调用 add 设置 {wait: true} 则会在服务器保存之后才触发 add 的调用
- 向服务器发送请求
add 并不会涉及到向服务器发送请求。
对 collection 的数据进行分组
对条目进行分类是常见的场景
如何在开发初期为 collection 填充测试数据
以 todos 应用为例,先以测试数据初始化一个 collection, 而不去使用 collection.fetch() 拉取后台数据。
var Todos = Backbone.Collection.extend({
model: Todo,
url: '/api/todos',
});
var todos = new Todos([
{title: 'coding'},
{title: 'running'}
]);
new TodosView(todos);
待后台接口完善后,再切换为
var todos = new Todos();
todos.fetch();
new TodosView(todos);
常见的 Events 有哪些
View 中监听 Model 的事件
this.listenTo(model, 'change:name', this.changeName) // model 的某个属性发生更改
this.listenTo(model, 'change', this.change) // model 的任意属性发生更改
View 中监听 Collection 的事件
this.listenTo(collection, 'change', this.addOne) // collection 中 model 属性变化
this.listenTo(collection, 'add', this.addOne) // model 添加到 collection 中
this.listenTo(collection, 'remove', this.removeOne) // model 从 collection 中移除
this.listenTo(collection, 'reset', this.render) // collection.fetch({reset: true})
请求相关的事件 (TODO: 测试失败)
request // 开始发起请求
sync // 请求成功
error // 请求失败
监听 View 模板中 element 事件
events: {
"click #commitBtn": 'commitInfo',
'mouseover .title': 'mouseoverTitle',
'keypress #commitForm': 'commitInfoOnEnter', // function(e) { if (e.which === 13) { this.commitInfo(); }}
}
参考 Events catalog - Backbone 官网文档
on 与 listenTo 的区别
从语法上看, on 是绑定当前 object 的事件
object.on(event, callback, [context])
listenTo 是绑定其他 object 的事件
object.listenTo(other, event, callback)
应用场景就很明确了
- listenTo 适合 view 绑定 collection, model 的变化事件
- on 适合 model 绑定自身属性更改事件对应的校验与刷新
例如, TodosView 中的事件绑定, 即监听了 collections 的事件
this.listenTo(app.todos, 'add', this.addOne);
this.listenTo(app.todos, 'reset', this.addAll);
this.listenTo(app.todos, 'change:completed', this.filterOne);
this.listenTo(app.todos, 'filter', this.filterAll);
this.listenTo(app.todos, 'all', this.render);
如果是在 collections 中监听,则
this.on('change:completed', this.filterOne);
参考 Collection add event listener in Backbone
views 分离
教程上通常不谈如何对页面进行 views 分离. 按照使用 AngularJS 的使用经验,将 view 分的越细越好,便于做单元测试。
无论是 AngularJS 还是 BackboneJS,要想提高开发效率、并降低维护成本,最关键的部分实际上是 view/controller 的划分。 如何将设计图上的界面区域分割为独立的 view/controller.
Backbone 需要手动添加 DOM 绑定,相对 AngularJS 增加了给 DOM 起 id, class 的成本,好恶心。查查有没有简单的做法。
本质上,每个 View 都或多或少地负责了一堆功能逻辑。如何组织好这堆逻辑呢?
To help organize this logic, we'll use the element controller pattern. The element controller pattern consists of two views: one controls a collection of items, while the other deals with each individual item.
我把 element controller pattern 称为“包工头模式”,即大的包工头负责管理小的包工头,小的包工头管理技工,技工只负责自己的那块业务即可。 之前,我一直称之为“view 分离模式”,后来想了想,这个名字不够形象,无法体现出分包的场景,还是叫“包工头模式”比较贴切。
以实际场景为例 AppView 是最外层的 View,通过 el: {id} 挂靠在 index 页面某个 element 上, AppView 里再包含具体的功能性 View。
参考
一个 view 想触发另一个 view 的 render 事件,直接监听事件是否合理?
可能的方案
- 还是将事件都绑定在 collection 上更合理 (参考 Events 中的 listenTo)
- 全局的 pub, sub 事件
单元测试
如何避免使用全局变量。
Backbonejs 与 AngularJS 的最大差距在于官网文档没有强调单元测试重要性,并且没有从架构上强制规避不可测试代码的出现。
参考
用 SeaJS / RequireJS 使 frontend MVC 代码模块化
什么是模块化?
可以理解为 python 内置的那些功能模块,例如 datetime, time, os 等。相当于把每个功能独立成一个 js 文件。
模块化相对传统的 js 组织结构的优势:
- 可以清楚的看出 js 文件间的相互依赖关系。例如,一个网页有两个 js 文件, jQuery.js 和 forum.js, forum.js 实际上依赖于 jQuery.js,但从 forum.js 里不能明确的看出它依赖于 jQuery.js.
- 按需加载。不会像传统方式那样下载所有的 js 文件,而是只下载使用到的。
- 无需自己组织 js 文件的加载顺序。因为当相互依赖的 js 文件越来越多时,加载顺序会变得越来越难以理清。
由于 SeaJS 是国人写的,英文参考资料较少,所以暂时使用 RequireJS (基本所有 backbone.js 的教程都会提及 RequireJS).
那么 frontend MVC 怎么会用到 RequireJS 呢?
就像 Django 的 view.py 会引用到 model.py 一样,backbone.js 的 view 也会用到 model, 这就是一个依赖关系。(同时也会用到 jQuery, backbone, underscore) 用法
在 backbone 中使用 ajax 的问题
在 ajax 的 success 或者 done 、error 中使用 this.<method_name> 总会报错。 显示该对象没有相应的方法。
原因是这里的 this 并不是代表 backbone 的对象。解决方法是在 ajax 外层使用:
var that = this;
然后在内层使用 that.<method_name>
The this reference within all callbacks is the object in the context option passed to $.ajax in the settings; if context is not specified, this is a reference to the Ajax settings themselves.
Problems with Backbone.Model callback and THIS
PS: 这个链接里的例子,挺好。很好的说明了 backbone 的使用。
- Javascript的this用法
- JavaScript, 5 ways to call a function
- Understanding _.each on backbone collections
View 中的 render 为何没有被主动调用
TODO
Router 与最外层的 AppView 是如何共存的
AppView 即 Top level view,最外层的 View。
要理清 Router 和 AppView 是如何共存的,首先要明确 Router 和 AppView 各自是做啥的。
Backbone 是非常自由的框架,同样的功能会有各式各样的实现,先谈谈我对 AppView 的理解。 AppView 存在的意义是,作为整个 app 的容器,namespace,保存 app 内公用的全局变量。 假设当前 app 有两个相对独立的页面,HomeView 和 AboutView,分别有对应的前端路径 #home #about, 默认进入 HomeView。显然 HomeView 和 AboutView 都是 AppView 下的子 View。
AppView 需要监听 click 事件,以确认需要切换到哪个子 View,然后通过 router.navigate() 触发。 所以很显然,把 router 置于 AppView 内是一种合理的做法。
问题来了,router 中的每个前端路径对应的 handler 内需要干些啥?
如果是实例化 HomeView,AboutView,前提是 AppView 已经 render 结束。这个不是问题,可以在 AppView 先 render 自己,然后实例化 Router。而 HomeView,AboutView 的 render 则是在 AppView 内找个挂载点。
如果 HomeView 中还有3个平级的 sub view 呢? 在 Router 的 handler 里就不好实现了,因为这个3个平级 sub view 需要基于 HomeView 已经 render 出的页面,是否能够引入 sub router?
仔细想了想,不用 sub router 同样可以实现。即每一个 sub view 在初始化时,都判断一下 HomeView 的实例是否存在, 如果存在就略过,不存在即实例化一个。这个过程在 router 的 handler 里去做。同时,HomeView 的初始化函数需要支持传入参数,以判断默认的 sub view 是哪个,以防止一闪而过的体验。
sub view 中跳转其他 sub view 的方法
// trigger 设置是否触发 router 中对应的 handler
// replace 设置是否将 URL 写入浏览器的 history 中
appView.router.navigate("pay", {trigger: true, replace: true});
参考
- Using the Backbone.js router to navigate through views modularized with require.js
- Module-Specific Subroutes in Backbone
mustache 还是 Backbone 内置的 underscore 模板
目前使用内置模板Underscore Template还没有遇到不顺手的地方
禁用链接的跳转效果
如果使用
<a href="#">I am a useless link</a>
会干扰前端路径, 替换为
<a href="javascript: void(0)">I am a useless link</a>
参考
- Developing Backbone.js Applications
- BACKBONE.JS 官网
- Backbone Views Using Mustache Templates
- Triggering Events Across Different Views In Backbone.js
- Backbone.js Tips And Patterns
- Web前端框架 Backbone.Marionette
微信关注我哦 👍
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式