A bit of Backbone

这个项目包含了一些我在学习Backbone的练习过的例子, 另外还有一些笔记.

比较有用的一些资料


Model

Backbone的理念中, 一个Model应该算是存在于客户端的与服器端resource/entity对应的一个JS对象, 定义一个Model很简单:

TodoItem = Backbone.Model.extend({
    defaults: {
        title: '',
        completed: false
    },
    validate: function (attributes) {
      // validation goes here
    }
})

创建一个Model的实例就是创建一个对象 var todoItem = new TodoItem(), 下面对定义在Model中的常用属性做一下说明(这些属性其实是一些钩子, Backbone希望我们去重写这些属性)

  • defaults

    通过这个属性来指定Model的默认值

    • 当创建Model实例的时候没有传入对象来初始化, 那么这个实例的属性就与defaults指定的属性值是一致的, 如:
    var todoItem = new TodoItem();
    console.log(JSON.stringify(todoItem.toJSON()));
    // {"title":"","completed":false}
    • 当创建Model实例时传入了对象, 那么这个对象会跟defaults进行merge
    var todoItem = new TodoItem({"newAttr": "valueOfNewAttr", "title": "Build a time machine"});
    console.log(JSON.stringify(todoItem.toJSON()));
    // {"newAttr":"valueOfNewAttr","title":"Build a time machine","completed":false}
  • validate

    将对Model的验证逻辑放在这个属性对应的方法中, 一般情况下, 我们不会直接调用这个validate方法, 而它扮演的角色有点像实现定义在父类中的抽象方法, 而这个方法会在Model的其他方法中被用到, 比如以下的这些方法:

    • isValid() 我们可以调用Model上的isValid()方法来判断这个Model上的属性值满足验证条件
    TodoItem = Backbone.Model.extend({
     defaults: {
         title: '',
         completed: false
     },
     validate: function (attributes, options) {
         if (attributes.title.indexOf("<") != -1) {
             return "html tag is now allowed is title"
         }
     }
    });
    var todoItem = new TodoItem({
        "title": "<script>...</script>"
    });
    console.log(todoItem.isValid());
    // false
    • save() or set()

      默认情况下调用Modelsave()方法会触发validate(), 可以通过在save()的时候使用option {validate: false}来跳过验证. 类似的, 也可以在set()时通过option {validate: true}来触发验证.

      validate()方法的返回值比较有趣, 如果验证通过了, 则什么都不用返回, 如果验证失败了, 则需要返回点什么(字符中或是对象都可以).

    validate()方法验证失败之后有两件事件会发现:

    1. validate()返回的结果添加到Model中, 可以通过model.validationError来访问
    2. Model上触发invalid事件, 并像model和error绑定对回调函数上

View

简单讲, View就是用来展示Model里面的数据的.

这样可以简单回顾一下我用过的一些前端JS框架是怎么处理数据展示的, 从最原始的jQuery开始: 只使用jQuery的时候, 当我们已经通过AJAX拿到数据之后, 我们可能需要先定位到需要显示数据的元素然后通过el.val('...')或者el.text('...')将数据手动设置上去. 当元素的值被更新之后需要将修改反应到后端时, 我们可能需要取到所有的元素的最新值然后发送到后台.

如上面那样通过操作对应的单个的DOM元素去设置值的方式写起来很繁琐, 如果返回的数据里面有十几甚至几十个条目的话, 写出来的代码更是毫无美感可言, 于是人们想到了使用JS模板, 通过一段通常是写在<head></head><script type="text/template" id="myTemplate">...</script>来表示最终要后面的HTML的结构, 在其中使用占位符来标识哪里部分需要被真正传入的数据替换. 定义好模板后, 当拿到AJAX的返回结果时, 我们只需要将数据填充到这个模板, 然后将生成的HTML添加到页面中就可以了, 一句话就可以搞定, 比如 $container.html(_.template($('#myTemplate'), ajaxReponseData)). 比较常用的模板引擎有mustache, underscore template

事情发展到这里还没完, 现在数据显示的问题解决的差不多了, 但是如果用户将某些DOM元素的值修改之后, 我们还需要将所有的元素的值读取一遍, 然后将这些值发送到后台去更新这条记录. 我们通过模板技术解决了繁琐的DOM设值, 但是数据的更新还是一个问题.

一起来看看Backbone的View是如何解决这个问题.

首先我们需要定义一个Backbone View, 定义的方法与Model非常的类似, 只需要继承View就可以了:

TodoItemView = Backbone.View.extend({
    template: _.template($("#todoItemTemplate").html()),
    initialize: function () {
        this.render();
    },
    render: function () {
        return this.$el.html(this.template(this.model.attributes));
    },
    events: {
        "click .toggle": "handleToggle",
        "click .destroy": "handleDestroy"
    },
    handleToggle: function () {
        this.model.set('completed', this.$('.toggle').prop('checked'));
    }
}

与上面提到的方式不同, Backbone View实现了细粒度的数据更新, 通过对对应的组件进行事件监听, 当某个组件的值被修改时, 只需要将被修改的属性更新到Model中.

下面介绍一下View中比较重要的几个属性(钩子):

  • initialize

    通常在这个属性对应的方法里面去生成最初的视图, 如上例中所求, 在initialize方法中将模板填充之后添加到View对应的HTML容器中.

  • el

    这个属性通常是一个_css selector_, 对应页面中这个View将被添加到HTML元素, 可以理解成是这个View的container, 个人理解是在创建Backbone Collection, View, Model的实例的时候, 都可以向其中传入一个对象, 这个对象的属性会与定义时的那些属性进行merge. 所以el既可以声明在View类的定义中, 也可以通过参加传入, 如 var todoItemView = new TodoItemView({el: jQuery('.todoItem'});View内部使用的时候, 通常是通过this.$el来操作这个容器, this.$el将会引用你配置的el对应的jQuery对象. 除了el之外, 还有一些其他的属性也是跟View的容器想关的:

    • tagName
    • className

    如果没有配置Viewel属性, 那么默认将使用一个DIV来包装View的内容, 这时候如果配置了tagName, 那么就会使用<tagName></tagName>来包装, 如果还有className的话, 那就变成了<tagName class="valueOfClassNameProp"></tagName>

  • this.$('css selector')

    一开始我也比较疑惑this.$()$()有什么区别, 看了源码之后才发现原来this.$()是在当时View的范围内查找元素, 是个挺实用的方法.

    $: function(selector) {
      return this.$el.find(selector);
    },
  • events

    这个属性应该是View所特有的, 它里面定义了一组元素上可能发生的事件以及对应的处理方法, 如:

    events: {
        "click .toggle": "handleToggle",
        "click .destroy": "handleDestroy"
    },
    handleToggle: function () {
        this.model.set('completed', this.$('.toggle').prop('checked'));
    }

    如在.toggle元素上点击时会执行handleToggle方法. 这种事件监听方式其实与jQuery.on()非常的类似:

    • jQuery.on(eventType, selector, handler)
    • "eventType selector": "handler"