一、Backbone概述
Backbone是一个基于MVC的前端js框架,它依赖于underscore.js和iquery.js的,所以在使用的时候需要引用三个js文件。
与之类似的MVC框架还有Angular.js,Knockout.js,Ember.js。
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
二、Backbone开发环境搭建
1.开发工具:推荐使用Sublime Text3
2.Web Server:我使用的是Tomcat
3.测试浏览器:Chrome
三、Backbone知识概要
Backbone主要包含以下几个模块:
![这里写图片描述](https://img-blog.csdn.net/20160527195522800)
Backbone是基于面向对象进行编程的。
路由(Backbone.Router)-> 模型(Backbone.Model)-> 视图(Backbone.View)
四、Model类和View类
1.Model类
是Backbone的模型层。
1.1原生的js对象
var _obj = {
'title':'task1',
'description':'description1'
}
浏览器测试:_obj
![这里写图片描述](https://img-blog.csdn.net/20160527200108865)
1.2.Backbone对Model的封装
var ToDoItem = Backbone.Model.extend({
});
var toDoItem = new ToDoItem(_obj);
浏览器测试:_toDomItem
总结:新建一个Model类,并实例化。其实,在Model类中我们可以定义一些成员属性和成员方法,这样它的所有实例都会具有这些成员属性和成员方法。如上例,只在toDoItem这个实例中传入了属性,只有该个实例具有该属性。
1.3.Model类的set/get方法
toDoItem.set('title','title2');
toDoItem.get('title');
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160527201058975)
1.4.通过Model类的attributes成员属性获得某个属性
除了使用toDoItem.get(‘title’)的方法获得属性之外,我们还可以使用Model类的成员属性attributes点语法获得获得某个属性。
toDoItem.attributes.title
1.5.Model类的toJSON()成员方法
该方法返回一个JSON字符串。
toDoItem.toJSON()
toDoItem.toJSON().title
toDoItem.toJSON().description
![这里写图片描述](https://img-blog.csdn.net/20160527201905472)
2.View类
Backbone的视图层,对Model层的数据进行渲染和展示。
2.1.View类的创建和实例化
参考2.2。
2.2.修改View类的成员属性【内置属性】
var ToDoItemView = Backbone.View.extend({
tagName:'li_tag',
className:'item_task',
id:'item1',
});
var toDoItemView = new ToDoItemView();
浏览器测试:
内置的成员属性,可以根据name修改对应的value值。
el介绍:
el指向界面的HTML元素,它一般有两种表现形式:一种是DOM形式【el】,一种是jQuery形式【$el】。我们可以通过重载View类的成员方法render()来渲染el内部的所有元素;或者通过remove()来移除el本身(包含其内部的所有元素)。
2.3.自定义View属性
除了使用View类的内置成员属性之外,我们还可以为View类自定义一些成员属性。不过自定义的属性必须放在attributes内,如下:
var ToDoItemView = Backbone.View.extend({
attributes:{
style:'color:red;'
},
id:'item01'
}
浏览器测试:
上面的red颜色设置给了cssText,可以展开进行查看。
区分:
Model类的实例,其属性可以通过set/get方法进行操作,还可以通过.attributes获得属性。
View类的实例,其属性都包含在el元素内,可以通过el的点语法获得某个特定的属性。
2.4.为el元素分派选择器
我们可以为el元素指向某个id选择器、类选择器、标签选择器。方便指向el内部标签的父元素。
了解View的render()方法, 该方法负责对数据进行渲染。
2.5.插入html代码到el指向的id选择器/标签选择器
下面以id选择器和标签选择器为例,其它选择器应该是类似的。
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p id=header1>header1</p>
<p id=header2>header2</p>
<script type="text/javascript">
var ToDoItemView = Backbone.View.extend({
id:'myId',
tagName:'p',
render:function(){
this.$el.html('<span>This is a new span.</span>');
return this;
}
});
var toDoItemView1 = new ToDoItemView({
el:'#header1'
});
toDoItemView1.render();
var toDoItemView2 = new ToDoItemView({
el:'#header2'
});
toDoItemView2.render();
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160527210316813)
分析:
1.插入html代码的时候,为View定义的tagName和id没有起到作用(区别于插入:插入的时候是有效果的)。
2.上面的插入会清空原来标签内部的所有代码,代替的是render()中插入的html代码。上面的示例也表明了:p标签内部原来是有文本数据的,但是执行插入的时候,原来的文本都被移除了。
上面是根据id选择器来插入的,还可以根据标签选择器进行插入。
var toDoItemView1 = new ToDoItemView({
el:'p'
});
toDoItemView1.render();
执行效果相同。
2.6.追加html代码到html标签内部
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p id=header1>header1</p>
<p id=header2>header2</p>
<script type="text/javascript">
var TodoItem = Backbone.Model.extend({});
var toDoItem = new TodoItem({
'title':'title1',
'description':'description1'
});
var ToDoItemView = Backbone.View.extend({
tagName:'p',
id:'myId',
render:function(){
this.$el.html('<span>This is a new span.</span>');
return this;
}
});
new ToDoItemView().render().$el.appendTo($('body'));
new ToDoItemView().render().$el.appendTo($('body'));
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
浏览器测试:
分析:
1.View的一个实例只能执行一次render(),如果使用同一个view实例多次执行追加,结果只追加了一次。
2.7.View的remove()方法
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p id=header1>header1</p>
<p id=header2>header2</p>
<script type="text/javascript">
var ToDoItemView = Backbone.View.extend({
tagName:'p',
id:'myId',
render:function(){
this.$el.html('<span>This is a new span.</span>');
return this;
}
});
var view1 = new ToDoItemView();
var view2 = new ToDoItemView();
view1.render().$el.appendTo($('body'));
view2.render().$el.appendTo($('body'));
view1.remove();
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
浏览器测试:
注意:结果显示只移除了一个p,所以一个View的实例,只能移除该实例的el本身。
3.Model和View的绑定
下面以一个实例,讲解如果将Backbone的Model和View连接起来,在View中获得Model实例,在Model中…
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p id=header1>header1</p>
<p id=header2>header2</p>
<script type="text/javascript">
var TodoItem = Backbone.Model.extend({});
var toDoItem = new TodoItem({
'title':'title1',
'description':'description1'
});
var ToDoItemView = Backbone.View.extend({
tagName:'p',
id:'myId',
render:function(){
this.$el.html('<span>hay,boy!'+this.model.get('title')+'</span>');
return this;
}
});
var view1 = new ToDoItemView({
model:toDoItem
});
var view2 = new ToDoItemView();
view1.render().$el.appendTo($('body'));
view2.render().$el.appendTo($('body'));
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
浏览器测试:
分析:
1.我们可以在新建view的实例的时候,绑定到某个model实例。
2.只有绑定了model的View才能获得可以通过this.model拿到该model实例。所以上面的代码view2在执行追加el元素的时候,是失败的。
其它:如果我们通过set方法来修改了Model实例的属性,必须调用View实例的render方法来重新渲染。
五、Model事件和View事件
1.Model事件
知识概要图如下:
![这里写图片描述](https://img-blog.csdn.net/20160527233337462)
1.1.Model事件的监听要点
1)Model内部对常用事件的监听做了封装,我们也可以自定义监听事件。
2)事件的监听用on,取消监听用off,一次性监听用once。
3)Model封装的常用内置监听事件:change()/destroy()/async()/invalid()等
4)change监听:用来监听Model对象的属性变化。当属性发生真实意义上的变化时,会自动触发change监听。我们可以通过change:’title’的形式监听Model对象中的单个属性’title’的变化。
5)我们可以自定义其它名称的监听,如Go监听,move监听等,只是名称而已。不过自定义的监听的触发需要手动调用trigger(‘监听名称’)。如:trigger(‘go’)
6)Model对象之间的监听使用:listenTo()
1.2.Model属性比较器
如果通过set函数修改属性的时候,属性值没有发生真实意义上的变化(即前后属性值是一样的),是不会触发change回调的。只有前后属性值不一样,才会触发change事件。
1.3.set()方法的可选属性silent
该属性值是boolean类型,用于设置是否自动触发change回调,默认false。
true:不触发
fasle:触发
model1.on('change',function(){
console.log('change event is triggered by set function()');
//this.changed对象,知道Model的哪些属性发生了变化
//console.log(this.changed);
//
if(this.hasChanged('title')){
console.log('title attribute is changed.');
}else{
console.log('title attribute is not changed.');
}
});
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160527235052703)
1.4.change()回调
知识要点:
//1.监听所有属性
model1.on('change',function(){...}
//2.监听单个属性
model1.on('change:title',function(){...}
//3.获得变化的属性
this.changed
//4.判断某个属性是否变化
if(this.hasChanged('title')){...}
change回调监听model属性的变化。
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<script type="text/javascript">
var TodoItem = Backbone.Model.extend({});
var model1 = new TodoItem({
'title':'title1',
'description':'description1'
});
model1.on('change',function(){
console.log('change event is triggered by set function()');
console.log(this.changed);
});
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
浏览器测试:
thi.changed对象:输出变化了的属性。
监听某个属性的变化:
//内置事件:只监听title属性的变化
model1.on('change:title',function(){
console.log('attribute is changed.');
if(this.hasChanged('title')){
console.log('title attribute is changed.');
}else{
console.log('title attribute is not changed.');
}
});
1.5.Model对象之间的监听:listenTo()
用途:一个Model对象监听另外一个Model对象的事件【内置事件或自定义事件】
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<script type="text/javascript">
var TodoItem = Backbone.Model.extend({});
var model1 = new TodoItem({
'title':'title1',
'description':'description1'
});
model1.on('change',function(){
if(this.hasChanged('title')){
console.log('title attribute is changed.');
}else{
console.log('title attribute is not changed.');
}
});
model1.on('lala',function(){
console.log('lala');
});
var model2 = new TodoItem({
});
model2.listenTo(model1,'change',function(){
console.log('listen to modle1 change event!');
console.log(this);
});
model2.listenTo(model1,'lala',function(){
console.log('listen to modle1 lala custom event!');
console.log(this);
});
</script>
</body>
</html>
<script type="text/javascript">
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160528000830249)
1.6.trigger()函数
手动触发自定义事件监听
modle1.on('lala',function(){
console('lala');
});
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160528000207303)
它也可以触发内置事件:
![这里写图片描述](https://img-blog.csdn.net/20160528000117646)
1.7.off函数
//取消监听
model1.off();//off所有监听事件
model1.off('change:title');//off具体的监听事件
model1.off('lala');
1.8.once函数
model1.once('change',function(){
console.log('lala');
});
model1.once('change:description',function(){
console.log('lala');
});
2.View事件
2.1.View监听Model的参数变化
代码:
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p>this is view event page </p>
<script type="text/javascript">
var BookModel = Backbone.Model.extend();
var book1 = new BookModel({
'title':'book1',
'description':'description1'
});
var BookView = Backbone.View.extend();
var bookView1 = new BookView();
bookView1.listenTo(book1, 'change', function(){
console.log('bookView1 has received the change from book1.');
});
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160529185053181)
2.2.Backbone构造函数 initialize:function()
构造函数在每一次实例化对象的时候都会首先调用。
代码:
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p>this is view event page </p>
<script type="text/javascript">
var BookModel = Backbone.Model.extend();
var book1 = new BookModel({
'title':'book1',
'description':'description1'
});
var BookView = Backbone.View.extend({
initialize:function(){
this.listenTo(this.model,'change',this.render);
},
render:function(){
console.log('model is changed.');
}
});
var bookView1 = new BookView({
model:book1
});
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
浏览器测试:
![这里写图片描述](https://img-blog.csdn.net/20160529185745080)
2.3.events 点击事件(单击【click】,双击【dblclick】)
代码:
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<p >this is view event page </p>
<p id='p1'>p1</p>
<p id='p2'>p2</p>
<script type="text/javascript">
var BookModel = Backbone.Model.extend();
var book1 = new BookModel({
'title':'book1',
'description':'description1'
});
var BookView = Backbone.View.extend({
initialize:function(){
this.listenTo(this.model,'change',this.render);
},
render:function(){
console.log('model is changed.');
},
events:{
'click':'handleClick',
'dblclick':'handleEldbClick'
},
handleClick:function(){
console.log('click event...');
},
handleEldbClick:function(){
console.log('double click event...');
}
});
var bookView1 = new BookView({
model:book1,
el:'#p1'
});
var bookView1 = new BookView({
model:book1,
el:'#p2'
});
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
浏览器测试:
上面实例化了两个view对象,分别指向p1和p2选择器,双击事件会先响应两次单击事件。
上面的示例是针对el元素本身的点击事件,如果只想对el元素下的某个元素指定点击事件的话,操作如下:
<p id='p1'>
<span>p1</span>
<a href="##">p1a</a>
</p>
el元素指向p元素,p元素下面有span和a两个元素。如果想要span和a都有单独的单击事件,有相同的双击事件,可以如下设置
格式:
'click tag选择器/id选择器/类选择器'
events:{
'click span':'handleElClick',
'click #a1':'handleElClick2',
'dblclick':'handleEldbClick'
},
handleElClick:function(){
console.log('click span...');
},
handleElClick2:function(){
console.log('click a...');
},
handleEldbClick:function(){
console.log('double click event...');
}
![这里写图片描述](https://img-blog.csdn.net/20160529230441675)
2.4.综合案例
目的:
①view中获得model的数据title和description,并渲染到界面body元素中。
②el元素外标签为div,并设置类选择器。
③对渲染到界面上的元素设置点击事件。
④点击按钮,将model从内存中移除,view监听移除事件,然后将自己从界面移除
代码:
<!DOCTYPE html>
<html>
<head>
<script src="underscore.js"></script>
<script src="jquery.js"></script>
<script src="backbone.js"></script>
</head>
<body>
<script type="text/javascript">
var BookModel = Backbone.Model.extend();
var book1 = new BookModel({
'title':'book1',
'description':'description1'
});
var book2 = new BookModel({
'title':'book2',
'description':'description2'
});
var BookView = Backbone.View.extend({
tagName:'div',
className:'todo-item',
initialize:function(){
console.log('initialize...');
this.listenTo(this.model,'destroy',this.remove);
},
render:function(){
var json = this.model.toJSON();
this.$el.html('<h3>'+json.title+'</h3><p>'
+ json.description+ '</p><button>delete</button>');
return this;
},
events:{
'click p':'handleElClick',
'click button':'handleButtonClick'
},
handleElClick:function(){
console.log('click p...');
},
handleButtonClick:function(){
console.log('click button...');
this.model.destroy();
}
});
var bookView1 = new BookView({
model:book1
});
var bookView2 = new BookView({
model:book2
});
bookView1.render().$el.appendTo($('body'));
bookView2.render().$el.appendTo($('body'));
</script>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
浏览器测试:点击第一个删除按钮,发现bookView1所选渲染的界面元素被删除了。
如果在console中手动调用book1.destroy()也是可以移除的,因为model的destroy事件被view监听了,view对监听的回调处理是将自己移除【this.remove】
![这里写图片描述](https://img-blog.csdn.net/20160529233706827)