您的位置:首页 > 其它

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,把视图对象可能的被外部的侦听也断开。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: