backbonejs

发布时间: 2015-12-12 20:45:34 作者: 大象笔记

缺乏架构气质的类库

如果想把 backbonejs 做为前端框架,还是算了,如果把 AngularJS 比作冲锋枪,那么 backbonejs 顶多是根树枝, 杀敌效率太低。 只有配合基于 backbonejs 的框架(例如,Marionette),才能提高生产力。

Models

哪些操作需要置于 Models 下呢?

Backbone.js Model 内置的一些核心函数

有个隐藏功能,即当 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 下呢?

Views.el 与 View.$el 的区别

避免 view render 的时候显示重复数据

render 之前,需要

this.$el.empty()

参考 Backbone.js collection view rendering duplicate items

sub view 带来的 memory leak

参考 Google Group 的一个实现

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 的集合

collection 发生变化的事件绑定放在哪里好?

采用排除法,逐一分析

在 collection 中指定特定的 view,但是如果指定了 view,那么这个 collection 就无法复用; 通过传参的方式传入 view,也不合理,如果一个页面上有联动同级 view,这种方式不可取

collection view 在实例化时,就需要传入一个 collection 的参数, 这个全局的 collection 实例可以定义在全局 app 对象中, 也可以定义在 app view 中。可行

是否需要 app view?

如果不使用 app view,那么全局 app 对象需要做的事情是,建立 collection 对象,collection view 对象,拉取 collection。完全不需要用到 app view。

add 与 create 的区别

create 会触发调用 add。create 的实际流程是

  1. 调用 add 设置 {wait: true} 则会在服务器保存之后才触发 add 的调用
  2. 向服务器发送请求

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)

应用场景就很明确了

例如, 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 事件,直接监听事件是否合理?

可能的方案

单元测试

如何避免使用全局变量。

Backbonejs 与 AngularJS 的最大差距在于官网文档没有强调单元测试重要性,并且没有从架构上强制规避不可测试代码的出现。

参考

用 SeaJS / RequireJS 使 frontend MVC 代码模块化

什么是模块化?

可以理解为 python 内置的那些功能模块,例如 datetime, time, os 等。相当于把每个功能独立成一个 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 的使用。

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});

参考

mustache 还是 Backbone 内置的 underscore 模板

目前使用内置模板Underscore Template还没有遇到不顺手的地方

禁用链接的跳转效果

如果使用

<a href="#">I am a useless link</a>

会干扰前端路径, 替换为

<a href="javascript: void(0)">I am a useless link</a>

参考

我是一名山东烟台的开发者,联系作者