要完整使用BackboneJS,须要引入如下jshtml
在没有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>
负责操做DOM,采用 OO 的思想来操做一个视图。jquery
只须要扩展视图构造函数 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);
假如,在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();
格式: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>
// 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;
在引入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 进行分离。
Model的做用
Collection的做用
Collection是Model的有序集合,和Model同样用于数据加载、保存,监听数据变化,还可使用 Underscore.js 提供的方法来操做Collection。
主要适用于list、table等视图的渲染。在本例中,就须要定义一个Collection来渲染列表,并监听Collection的变化。
// Goal Model var Goal = Backbone.Model.extend({ defaults: { title: '' } }); // Goal Collection var GoalCollection = Backbone.Collection.extend({ model: Goal, });
下面这段代码,有一些地方是相同的,为了不重复代码,可使用模板来渲染。
<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>
把重复的部分抽出来,定义模板时使用<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中定义GoalView,用于生成每个Goal对应的<li>
节点。
BackboneJS中的template实际上调用的是underscore.js的template方法,该方法能够将 JavaScript 模板编译为能够用于页面呈现的函数,它返回的是一个函数。
_.template(templateString, [settings])
而后在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);
在AppView中,修改addGoal的添加模式,将原来的直接操做DOM,修改成经过data的变化来触发DOM的渲染。
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的变化进行监听。
在AppView中,经过listenTo()方法,监听Collection的变化,当Collection发生变化时,触发内部的某个方法。
object.listenTo(other, event, callback)
listenTo 用于一个对象,监听另外一个对象的变化
中止监听使用stopListening
object.stopListening([other], [event], [callback])
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 });
效果相同
在上一步中,已经给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对应的节点,同时还能中止节点上绑定的事件监听。
在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。
能够在使用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); },