backbone.js 教程(1) View & Model & Collection

Backbone.js Overview

  • 它由Jeremy Ashkenas开发,最初发行于2010-10-13
  • 它是一个轻量的JavaScript类库,只依赖于underscore.js,非强制依赖于jquery,大小只有7.6Kb,做为一个框架级的js文件,它已经很是小了
  • 它提供了Model-View-Presenter(MVP)的框架模式,能够帮助前端开发搭建一个层次清晰的Web应用框架
  • 它提供了models和collections来封装数据结构,提供了views来操做DOM,提供了自定义事件将数据结构和DOM操做绑定在一块儿

Environment Setup

要完整使用BackboneJS,须要引入如下jshtml

  • Underscore.js(>= 1.8.3)或者lodash.js
  • Jquery.js(>= 1.11.0)
  • Json2.js(若是须要支持IE)

在没有npm的环境下,能够下载压缩包或者使用CDN。前端

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.0/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>

Bakbone.js View

负责操做DOM,采用 OO 的思想来操做一个视图。jquery

Backbone.js View API

  • extend 每个自定义的View都必须由Backbone.View类extend而来,override父类中的属性
  • initialize() View的构造函数,每当new一个实例时,就会调用该方法
  • render() 一般将view的渲染逻辑写在此方法中,并在initialize中调用它
  • template() View渲染时的模板,可使用underscore的模板,也可使用其余任意JS模板引擎
  • el View对应的DOM对象,可使用id选择器,类选择器来定义
  • $el View对应的jQuery对象,方便使用jQuery的方法来操做DOM
  • tagName View对应的DOM节点的标签名称,默认是“div”
  • id View对应的DOM节点的id属性
  • className View对应的DOM节点的class属性
  • events 给View绑定事件
  • remove() 移除一个view,其el将从DOM中移出,绑定的事件也将中止监听

Create a View

只须要扩展视图构造函数 Backbone.View, 传入Dom相关的属性。ajax

示例 假如,须要在DOM中动态添加一个id=“root”的div。npm

不使用backbone.js,咱们一般这样实现。api

// app.js
function addRoot() {
  var el = document.createElement('div');
  el.id = 'root';
  el.innerHTML = 'Hello Backbone!!!';
  document.body.appendChild(el);
}
addRoot();

使用backbone.js,咱们这样实现:数据结构

// app.js
var AppView = Backbone.View.extend({
  tagName: 'div',
  id: 'root',
  initialize: function () {
    this.render();
  },
  render: function () {
    this.el.innerHTML = 'Hello Backbone!!!';
    return this;
  }
});

var appView = new AppView();
document.body.appendChild(appView.el);
  • tagName 指定这个element 是一个div
  • id 指定这个div的id属性值
  • 当 调用 new AppView() 时,执行initialize() 函数
  • render() 函数用于渲染这个element

Get Existed Element

假如,在html中已经定义了div#root这个element,想修改它的内容。app

使用Backbone.js怎么来操做这个element呢?框架

// index.html
<body>
  <div id="root">loading...</div>
</body>
var AppView = Backbone.View.extend({
  el: '#root',
  initialize: function () {
    this.render();
  },
  render: function () {
    this.el.innerHTML = 'Hello Backbone!!!';
    return this;
  }
});
var appView = new AppView();

Bind events

格式:ide

events: {
    'event1 selector1': 'function name1',
    'event2 selector2': 'function name2',
        ...
}

示例 有这样一个小应用,在input中输入后,回车,添加一个new goal;点击每个goal后面的remove,移除此项目。

// index.html
<div id="root" class="color-dark">
  <header>
    <h2>My Life Goals</h2>
    <input id="new-goal" type="text" placeholder="add a new goal">
  </header>
  <ul id="goal-list">
    <li class="goal-item">Goal one <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal two <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal three <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal four <a class="btn-remove">remove</a></li>
  </ul> 
</div>
  • 给input绑定一个 keypress 事件
  • 给每个 .btn-remove 绑定一个click事件
// app.js
var AppView = Backbone.View.extend({
  el: '#root',
  … …
  events: {
    'keypress #new-goal': 'addGoal',
    'click .btn-remove': 'clear',
  },
  addGoal: function(ev) {
    if (ev.keyCode != 13) return;
    console.log('addGoal');
    // To do
  },
  clear: function() {
    // To do
  }
});
var appView = new AppView;

How to change view

在引入Backbone.js的Model以前,咱们能够这样来实现 addGoal 方法。

addGoal: function(ev) {
  if (ev.keyCode != 13) return;
  var newGoal = $('#new-goal').val();
  if(newGoal === '') return;
  var goalHtml = '<li class="goal-item">'+   newGoal +'<a class="btn-remove">remove</a></li>';
  $('#goal-list').append(goalHtml);
  $('#new-goal').val('');
}

在Backbone.js 出现以前,当数据发生变化视图须要从新渲染时,咱们一般使用js或jQuery来进行DOM操做,改变展现的内容。

这样作data和视图渲染混在一块儿,显得很乱;并且,若是视图上要显示的属性不少,拼接的代码就很长很长。

因此,使用Backbone.js 的Model和Collection 将data和View 进行分离。

Bakbone.js Model & Collection

Model的做用

  • 封装数据结构
  • 处理业务逻辑
  • 从server 加载、保存数据
  • 当data发生变化时触发事件,好比从新渲染视图

Collection的做用

Collection是Model的有序集合,和Model同样用于数据加载、保存,监听数据变化,还可使用 Underscore.js 提供的方法来操做Collection。

主要适用于list、table等视图的渲染。在本例中,就须要定义一个Collection来渲染列表,并监听Collection的变化。

定义Model和Collection

// Goal Model
var Goal = Backbone.Model.extend({
  defaults: {
    title: ''
  }
});

// Goal Collection
var GoalCollection = Backbone.Collection.extend({
  model: Goal,
});

使用template

下面这段代码,有一些地方是相同的,为了不重复代码,可使用模板来渲染。

<ul id="goal-list">
    <li class="goal-item">Goal one <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal two <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal three <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal four <a class="btn-remove">remove</a></li>
  </ul>

在html中定义模板

把重复的部分抽出来,定义模板时使用<script>标签,但这里的type是text/template,而后给它一个id,用于在View中经过id来获取它。

<body>
  <div id="root" class="color-dark">
    <header>
      <h2>My Life Goals</h2>
      <input id="new-goal" type="text" placeholder="add a new goal">
    </header>
    <ul id="goal-list">
      <!-- template -->
    </ul>
    </div>

  <script type="text/template" id="item-template">
    <li class="goal-item"><%= title %><a class="btn-remove">remove</a></li>
  </script>
</body>
  • <%= %>表示插入变量
  • <% %>表示插入任意JS代码段
  • <%- %>表示插值并进行转义

View中定义解析函数template()

在js中定义GoalView,用于生成每个Goal对应的<li>节点。

BackboneJS中的template实际上调用的是underscore.js的template方法,该方法能够将 JavaScript 模板编译为能够用于页面呈现的函数,它返回的是一个函数

_.template(templateString, [settings])

render时调用template

而后在render中调用template方法,把model对象做为参数传入。

// app.js

// Goal Model
var GoalModel = Backbone.Model.extend({
  defaults: {
    title: '' // 默认属性值
  }
});

// Goal Collection
var GoalCollection = Backbone.Collection.extend({
  model: GoalModel,
});

var GoalView = Backbone.View.extend({
  tagName: 'li',
  initialize: function () {
    this.render();
  },
  template: function () {
    return _.template($('#item-template').html()); //根据模板的id来获取模板定义的内容
  },
  render: function () {
    this.$el.html(this.template()(this.model.toJSON()));
  },
  events: {    
    'click .btn-remove': 'clear', // 绑定事件
  },
  clear: function() {
    // To do
  }
});

var AppView = Backbone.View.extend({
  el: '#root',
  … …
  events: {
    'keypress #new-goal': 'addGoal',
  },
  addGoal: function(ev) {
    if (ev.keyCode != 13) return;
    console.log('addGoal');
    // To do
  },
  
});

测试效果:

var view = new GoalView({ model: {title: 'My first goal'} });
this.$("#goal-list").append(view.$el);

bind Collection to View

在AppView中,修改addGoal的添加模式,将原来的直接操做DOM,修改成经过data的变化来触发DOM的渲染。

  • 在AppView中实例化一个GoalCollection,命名为goalList
  • Keypress事件触发时,修改goalList,这里调用了Backbone.Collection中的push()方法
var AppView = Backbone.View.extend({
  el: '#root',
  initialize: function () {
    this.goalList = new GoalCollection(); 
    this.render();
},
  render: function () {
    return this;
  },
  events: {
   'keypress #new-goal': 'addGoal'
  },
  addGoal: function (ev) {
    if (ev.keyCode != 13) return;
    var inputVal = $('#new-goal').val(); // 获取输入的值
    if (inputVal === '') return;
    this.goalList.push({ title: inputVal }); // push到Collection
    $('#new-goal').val('');
  },
});

可是,此时,你会发现虽然goalList发生了变化,可是页面并无跟着渲染。

由于,View并无对Collection的变化进行监听。

Model 和 Collection的事件监听

View 监听 Model或Collection的变化

在AppView中,经过listenTo()方法,监听Collection的变化,当Collection发生变化时,触发内部的某个方法。

object.listenTo(other, event, callback)

listenTo 用于一个对象,监听另外一个对象的变化

中止监听使用stopListening

object.stopListening([other], [event], [callback])

监听add事件
var AppView = Backbone.View.extend({
    el: '#root',
    initialize: function () {
        this.goalList = new GoalCollection();
        this.render();
        this.listenTo(this.goalList, 'add', this.addOne);
        // or 
        // this.goalList.on('add', this.addOne, this);
    },
    render: function () {
        return this;
    },
    events: {
        'keypress #new-goal': 'addGoal'
    },
    addGoal: function (ev) {
        if (ev.keyCode != 13) return;
        var inputVal = $('#new-goal').val();
        if (inputVal === '') return;
        this.goalList.push({ title: inputVal });
        // or this.goalList.add({ title: inputVal });

        $('#new-goal').val('');
    },
    addOne: function (goal) {
        var view = new GoalView({ model: goal });
        this.$("#goal-list").append(view.$el);
    }
});

这里为何监听的event是 add,而不是 push?

由于push()方法底层其实调用的是add()方法。

this.goalList.push({ title: inputVal });

修改成

this.goalList.add({ title: inputVal });

效果相同

监听destroy事件

在上一步中,已经给GoalView绑定了Goal这个Model,那么在View中就可使用Model来控制View的渲染。在GoalView中须要监听GoalModel的变化,goalModel移除时,销毁视图。

var GoalView = Backbone.View.extend({
    tagName: 'li',
    initialize: function () {
        this.render();
        this.listenTo(this.model, 'destroy', this.remove);
        //or this.model.on('destroy', this.remove, this);
    },
    template: function () {
        return _.template($('#item-template').html());
    },
    render: function () {
        console.log('model', this.model.toJSON());
        this.$el.html(this.template()(this.model.toJSON()));
    },
    events: {
        'click .btn-remove': 'clear',
    },

    clear: function() {
        this.model.destroy(); 
    }
});

destroy model后,view 也会从DOM中移除,同时绑定的事件也会中止监听。

this.remove 是View 内置的函数。

remove()方法不只能够从DOM中移除view对应的节点,同时还能中止节点上绑定的事件监听。

Model或Collection 自我监听变化

在AppView中,还能够经过调用on()方法,让Collection监听本身的变化。

object.on(event, callback, [context])

这种用法是本身监听本身。

若是想中止监听,使用off()方法

object.off([event], [callback], [context])

this.listenTo(this.goalList, 'add', this.addOne);

等效于

this.goalList.on('add', this.addOne, this);

this.listenTo(this.model, 'destroy', this.remove);

等效于

this.model.on('destroy', this.remove, this);

为何要传入context?

由于调用on()方法的是this.goalList,若是不传入context,那么在addOne()调用时,默认的this指代的是this.goalList,而不是AppView的实例了。

所以,为了保证上下文都是View的实例,须要传入context。

使用 bind() 或 bindAll() 修改context

能够在使用on()时,不传入context,而使用 .bind() 或 .bindAll() 来绑定context

在使用bind时,必须使用bind返回的函数

// 使用 bind
initialize: function () {
    this.render();
    this.remove = _.bind(this.remove, this); // 返回值是一个函数
    this.model.on('destroy', this.remove);
},

使用bindAll很是方便,没必要考虑返回值

// 使用 bindAll
initialize: function () {
    this.render();
    _.bindAll(this, 'remove', 'clear'); // 能够同时改变多个函数的context
    this.model.on('destroy', this.remove);
},
相关文章
相关标签/搜索