Backbone.Events
2016-02-06 16:09
344 查看
Backbone.Events就是事件实现的核心,它可以让对象拥有事件能力
var Events = Backbone.Events = { .. }
对象通过listenTo侦听其他对象,通过trigger触发事件。可以脱离Backbone的MVC,在自定义的对象上使用事件
var model = _.extend({},Backbone.Events);
var view = _.extend({},Backbone.Events);
view.listenTo(model,'custom_event',function(){ alert('catch the event') });
model.trigger('custom_event');
Backbone的Model和View等核心类,都是继承自Backbone.Events的。例如Backbone.Model:
var Events = Backbone.Events = { .. }
var Model = Backbone.Model = function(attributes, options) {
...
};
_.extend(Model.prototype, Events, { ... })
从原理上讲,事件是这么工作的:
被侦听的对象维护一个事件数组_event,其他对象在调用listenTo时,会将事件名与回调维护到队列中:
一个事件名可以对应多个回调,对于被侦听者而言,只知道回调的存在,并不知道具体是哪个对象在侦听它。当被侦听者调用trigger(name)时,会遍历_event,选择同名的事件,并将其下面所有的回调都执行一遍。
需要额外注意的是,Backbone的listenTo实现,除了使被侦听者维护对侦听者的引用外,还使侦听者也维护了被侦听者。这是为了在恰当的时候,侦听者可以单方面中断侦听。因此,虽然是循环引用,但是使用Backbone的合适的方法可以很好的维护,不会有问题,在后面的内存泄露部分将看到。
另外,有时只希望事件在绑定后,当回调发生后,就接触绑定。这在一些对公共模块的引用时很有用。listenToOnce可以做到这一点
var Task = Backbone.Model.extend({})
var TaskView = Backbone.View.extend({
tagName: 'tr',
template: _.template('<td><%= id %></td><td><%= summary %></td><td><%= description %></td>'),
initialize: function(){
this.listenTo(this.model,'change',this.render);
},
render: function(){
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
})
var TaskCollection = Backbone.Collection.extend({
url: 'http://api.test.clippererm.com/api/testtasks',
model: Task,
comparator: 'summary'
})
var TaskCollectionView = Backbone.View.extend({
initialize: function(){
this.listenTo(this.collection, 'add',this.addOne);
this.listenTo(this.collection, 'reset',this.render);
},
addOne: function(task){
var view = new TaskView({ model : task });
this.$el.append(view.render().$el);
},
render: function(){
var _this = this;
//简单粗暴的将DOM清空
//在sort事件触发的render调用时,之前实例化的TaskView对象会泄漏
this.$el.empty();
this.collection.each(function(model){
_this.addOne(model);
})
return this;
}
})
使用下面的测试代码,并结合Chrome的堆内存快照来证明:
var tasks = null;
var tasklist = null;
$(function () {
// body...
$('#start').click(function(){
tasks = new TaskCollection();
tasklist = new TaskCollectionView({
collection : tasks,
el: '#tasklist'
})
tasklist.render();
tasks.fetch();
})
$('#refresh').click(function(){
tasks.fetch({ reset : true });
})
$('#sort').click(function(){
//将侦听sort放在这里,避免第一次加载数据后的自动排序,触发的sort事件,以至于混淆
tasklist.listenToOnce(tasks,'sort',tasklist.render);
tasks.sort();
})
})
点击开始,使用Chrome的'Profile'下的'Take Heap Snapshot'功能,查看当前堆内存情况,使用child类型过滤,可以看到Backbone对象实例一共有10个(1+1+4+4):
那么,为什么每次排序后,之前的TaskView无法释放呢。因为TaskView的实例都会侦听model,导致model对新创建的TaskView的实例存在引用,所以旧的TaskView无法删除,又创建了新的,导致内存不断上涨。而且由于引用存在于change事件的回调队列里,model每次触发change都会通知旧的TaskView实例,导致执行很多无用的代码。那么如何改进呢?
修改TaskCollectionView:
var TaskCollectionView = Backbone.View.extend({
initialize: function(){
this.listenTo(this.collection, 'add',this.addOne);
this.listenTo(this.collection, 'reset',this.render);
//初始化一个view数组以跟踪创建的view
this.views =[]
},
addOne: function(task){
var view = new TaskView({ model : task });
this.$el.append(view.render().$el);
//将新创建的view保存起来
this.views.push(view);
},
render: function(){
var _this = this;
//遍历views数组,并对每个view调用Backbone的remove
_.each(this.views,function(view){
view.remove().off();
})
//清空views数组,此时旧的view就变成没有任何被引用的不可达对象了
//垃圾回收器会回收它们
this.views =[];
this.$el.empty();
this.collection.each(function(model){
_this.addOne(model);
})
return this;
}
})
Backbone的View有一个remove方法,这个方法除了删除View所关联的DOM对象,还会阻断事件侦听,它通过在listenTo方法时记录下来的那些被侦听对象(上文事件原理中提到),来使这些被侦听的对象删除对自己的引用。在remove内部使用事件基类的stopListening完成这个动作。
上面的代码使用一个views数组来跟踪新创建的TaskView对象,并在render的时候,依次调用这些视图对象的remove,然后清空数组,这样这些TaskView对象就能得到释放。并且,除了调用remove,还调用了off,把视图对象可能的被外部的侦听也断开。
var Events = Backbone.Events = { .. }
对象通过listenTo侦听其他对象,通过trigger触发事件。可以脱离Backbone的MVC,在自定义的对象上使用事件
var model = _.extend({},Backbone.Events);
var view = _.extend({},Backbone.Events);
view.listenTo(model,'custom_event',function(){ alert('catch the event') });
model.trigger('custom_event');
Backbone的Model和View等核心类,都是继承自Backbone.Events的。例如Backbone.Model:
var Events = Backbone.Events = { .. }
var Model = Backbone.Model = function(attributes, options) {
...
};
_.extend(Model.prototype, Events, { ... })
从原理上讲,事件是这么工作的:
被侦听的对象维护一个事件数组_event,其他对象在调用listenTo时,会将事件名与回调维护到队列中:
一个事件名可以对应多个回调,对于被侦听者而言,只知道回调的存在,并不知道具体是哪个对象在侦听它。当被侦听者调用trigger(name)时,会遍历_event,选择同名的事件,并将其下面所有的回调都执行一遍。
需要额外注意的是,Backbone的listenTo实现,除了使被侦听者维护对侦听者的引用外,还使侦听者也维护了被侦听者。这是为了在恰当的时候,侦听者可以单方面中断侦听。因此,虽然是循环引用,但是使用Backbone的合适的方法可以很好的维护,不会有问题,在后面的内存泄露部分将看到。
另外,有时只希望事件在绑定后,当回调发生后,就接触绑定。这在一些对公共模块的引用时很有用。listenToOnce可以做到这一点
内存泄漏
事件机制可以很好的带来代码维护的便利,但是由于事件绑定会使对象之间的引用变得复杂和错乱,容易造成内存泄漏。下面的写法就会造成内存泄漏:var Task = Backbone.Model.extend({})
var TaskView = Backbone.View.extend({
tagName: 'tr',
template: _.template('<td><%= id %></td><td><%= summary %></td><td><%= description %></td>'),
initialize: function(){
this.listenTo(this.model,'change',this.render);
},
render: function(){
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
})
var TaskCollection = Backbone.Collection.extend({
url: 'http://api.test.clippererm.com/api/testtasks',
model: Task,
comparator: 'summary'
})
var TaskCollectionView = Backbone.View.extend({
initialize: function(){
this.listenTo(this.collection, 'add',this.addOne);
this.listenTo(this.collection, 'reset',this.render);
},
addOne: function(task){
var view = new TaskView({ model : task });
this.$el.append(view.render().$el);
},
render: function(){
var _this = this;
//简单粗暴的将DOM清空
//在sort事件触发的render调用时,之前实例化的TaskView对象会泄漏
this.$el.empty();
this.collection.each(function(model){
_this.addOne(model);
})
return this;
}
})
使用下面的测试代码,并结合Chrome的堆内存快照来证明:
var tasks = null;
var tasklist = null;
$(function () {
// body...
$('#start').click(function(){
tasks = new TaskCollection();
tasklist = new TaskCollectionView({
collection : tasks,
el: '#tasklist'
})
tasklist.render();
tasks.fetch();
})
$('#refresh').click(function(){
tasks.fetch({ reset : true });
})
$('#sort').click(function(){
//将侦听sort放在这里,避免第一次加载数据后的自动排序,触发的sort事件,以至于混淆
tasklist.listenToOnce(tasks,'sort',tasklist.render);
tasks.sort();
})
})
点击开始,使用Chrome的'Profile'下的'Take Heap Snapshot'功能,查看当前堆内存情况,使用child类型过滤,可以看到Backbone对象实例一共有10个(1+1+4+4):
那么,为什么每次排序后,之前的TaskView无法释放呢。因为TaskView的实例都会侦听model,导致model对新创建的TaskView的实例存在引用,所以旧的TaskView无法删除,又创建了新的,导致内存不断上涨。而且由于引用存在于change事件的回调队列里,model每次触发change都会通知旧的TaskView实例,导致执行很多无用的代码。那么如何改进呢?
修改TaskCollectionView:
var TaskCollectionView = Backbone.View.extend({
initialize: function(){
this.listenTo(this.collection, 'add',this.addOne);
this.listenTo(this.collection, 'reset',this.render);
//初始化一个view数组以跟踪创建的view
this.views =[]
},
addOne: function(task){
var view = new TaskView({ model : task });
this.$el.append(view.render().$el);
//将新创建的view保存起来
this.views.push(view);
},
render: function(){
var _this = this;
//遍历views数组,并对每个view调用Backbone的remove
_.each(this.views,function(view){
view.remove().off();
})
//清空views数组,此时旧的view就变成没有任何被引用的不可达对象了
//垃圾回收器会回收它们
this.views =[];
this.$el.empty();
this.collection.each(function(model){
_this.addOne(model);
})
return this;
}
})
Backbone的View有一个remove方法,这个方法除了删除View所关联的DOM对象,还会阻断事件侦听,它通过在listenTo方法时记录下来的那些被侦听对象(上文事件原理中提到),来使这些被侦听的对象删除对自己的引用。在remove内部使用事件基类的stopListening完成这个动作。
上面的代码使用一个views数组来跟踪新创建的TaskView对象,并在render的时候,依次调用这些视图对象的remove,然后清空数组,这样这些TaskView对象就能得到释放。并且,除了调用remove,还调用了off,把视图对象可能的被外部的侦听也断开。
相关文章推荐
- Tomcat【4】(tomcat在eclipse的配置)
- POJ 2251宽搜、
- 广搜练习(一)
- UVa--10976 Fractions Again(枚举)
- hdu1224 Free DIY Tour (dp)
- 业务流程节点信息提示
- 泛型与集合类型
- 业务流程节点信息构件
- 寻找山顶
- javascript+css3 实现动态按钮菜单特效
- JavaScript基础精华03(String对象,Array对象,循环遍历数组,JS中的Dictionary,Array的简化声明)
- JavaScript基础精华03(String对象,Array对象,循环遍历数组,JS中的Dictionary,Array的简化声明)
- POJ 2104 K-th Number
- 【Android】5.0 第5章 常用基本控件--本章示例主界面
- poj--2236--Wireless Network(并查集)
- Java [Leetcode 257]Binary Tree Paths
- poj--2236--Wireless Network(并查集)
- 短信验证码服务
- 鸡兔同笼问题
- 三消游戏核心逻辑的一种实现