backbonejs

更新日期: 2020-04-27 阅读次数: 19067 字数: 2930 分类: BackboneJS

缺乏架构气质的类库

如果想把 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

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

  • 后台 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 的实际流程是

  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)

应用场景就很明确了

  • 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 的使用。

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>

参考

关于作者 🌱

我是来自山东烟台的一名开发者,有敢兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式