Backbone运用总结

最先在项目中大范围运用backbone,一起磕磕碰碰,边做边进修边体味,有一些心得和体味,纪录在本文中。原文:Backbone运用总结

事宜模子及其道理

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运用总结》

Backbone的Model和View等中心类,都是继续自Backbone.Events的。比方Backbone.Model:

var Events = Backbone.Events = { .. }

var Model = Backbone.Model = function(attributes, options) {
    ...
};

_.extend(Model.prototype, Events, { ... })

从道理上讲,事宜是这么事情的:

被侦听的对象保护一个事宜数组_event,其他对象在挪用listenTo时,会将事宜名与回调保护到行列中:

《Backbone运用总结》

一个事宜名可以对应多个回调,关于被侦听者而言,只知道回调的存在,并不知道详细是哪一个对象在侦听它。当被侦听者挪用trigger(name)时,会遍历_event,遴选同名的事宜,并将其下面一切的回调都实行一遍。

须要分外注重的是,Backbone的listenTo完成,除了使被侦听者保护对侦听者的援用外,还使侦听者也保护了被侦听者。这是为了在适当的时刻,侦听者可以片面中缀侦听。因而,虽然是轮回援用,然则运用Backbone的适宜的要领可以很好的保护,不会有题目,在背面的内存走漏部份将看到。

别的,偶然只愿望事宜在绑定后,当回调发生后,就打仗绑定。这在一些对大众模块的援用时很有效。listenToOnce可以做到这一点

与服务器同步数据

backbone默许完成了一套与RESTful作风的服务端同步模子的机制,这套机制不仅可以减轻开发人员的事情量,而且可以使模子变得更加硬朗(在种种非常下仍能坚持数据一致性)。不过,要真正发挥这个功用,一个与之婚配的服务端完成是很主要的。为了申明题目,假定服务端有以下REST作风的接口:

  • GET /resources 猎取资本列表
  • POST /resources 建立一个资本,返回资本的悉数或部份字段
  • GET /resources/{id} 猎取某个id的资本概况,返回资本的悉数或部份字段
  • DELETE /resources/{id} 删除某个资本
  • PUT /resources/{id} 更新某个资本的悉数字段,返回资本的悉数或部份字段
  • PATCH /resources/{id} 更新某个资本的部份字段,返回资本的悉数或部份字段

backbone会运用到上面这些HTTP要领的处所主要有以下几个:

  • Model.save() 逻辑上,依据当前这个model的是不是具有id来推断应当运用POST照样PUT,假如model没有id,示意是新的模子,将运用POST,将模子的字段悉数提交到/resources;假如model具有id,示意是已存在的模子,将运用PUT,将模子的悉数字段提交到/resources/{id}。当传入options包含patch:true的时刻,save会发生PATCH
  • Model.destroy() 会发生DELETE,目的url为/resources/{id},假如当前model不包含id时,不会与服务端同步,因为此时backbone以为model在服务端尚不存在,不须要删除
  • Model.fetch() 会发生GET,目的url为/resources/{id},并将取得的属性更新model。
  • Collection.fetch() 会发生GET,目的url为/resources,并对返回的数组中的每一个对象,自动实例化model
  • Collection.create() 现实将挪用Model.save

options参数存在于上面任何一个要领的参数列表中,经由过程options可以修正backbone和ajax请求的一些行动,可以运用的options包含:

  • wait: 可以指定是不是守候服务端的返回效果再更新model。默许状况下不守候
  • url: 可以掩盖掉backbone默许运用的url花样
  • attrs: 可以指定保存到服务端的字段有哪些,合营options.patch可以发生PATCH对模子举行部份更新
  • patch: 指定运用部份更新的REST接口
  • data: 会被直接通报给jquery的ajax中的data,可以掩盖backbone一切的对上传的数据掌握的行动
  • 其他: options中的任何参数都将直接通报给jquery的ajax,作为其options

backbone经由过程Model的urlRoot属性或许是Collectionurl属性得知详细的服务端接口地点,以便提议ajax。在Model的url默许完成中,Model除了会考核urlRoot,第二遴选会是Model地点Collection的url,一切偶然只须要在Collection内里誊写url就可以了。

Backbone会依据与服务端要举行什么范例的操纵,决议是不是要增加idurl背面,以下代码是Model的默许url完成:

url: function () {
    var base =
      _.result(this, 'urlRoot') ||
      _.result(this.collection, 'url') ||
      urlError();
    if (this.isNew()) return base;
    return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},

个中的正则式/([^\/])$/是个很奇妙的处置惩罚,它处理了url末了是不是包含'/'的不确定性。

这个正则婚配的是行末的非/字符,如许,像/resources如许的目的会婚配s,然后replace中运用分组编号$1捕捉了s,将s替换为s/,如许就自动加上了缺失的/;而当/resources/如许目的却没法婚配到效果,也就不须要替换了。

Model和Collection的关联

在backbone中,即使一类的模子实例的确是在一个鸠合内里,也并没有强迫请求运用鸠合类。然则运用鸠合有一些分外的优点,这些优点包含:

url继续

Model属于Collection后,可以继续Collection的url属性。上面一节已提到了

underscore鸠合才

Collection沿用了underscore90%的鸠合和数组操纵,使得鸠合操纵极为轻易:

// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty', 'chain', 'sample'];

Backbone奇妙的运用下面的代码将这些要领附加到Collection中:

// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function (method) {
    Collection.prototype[method] = function () {
        var args = slice.call(arguments);   //将参数数组转化成真正的数组
        args.unshift(this.models);          //将Collection真正用来保护鸠合的数组,作为第一个个参数
        return _[method].apply(_, args);    //运用apply挪用underscore的要领
    };
});

自动侦听和转发鸠合中的Model事宜

鸠合可以自动侦听并转发鸠合中的元素的事宜,另有一些事宜鸠合会做响应的特别处置惩罚,这些事宜包含:

  • destroy 侦听到元素的destroy事宜后,会自动将元素从鸠合中移除,并激发remove事宜
  • change:id 侦听到元素的id属性被change后,自动更新内部对model的援用关联

自动模子组织

应用Collectionfetch,可以加载服务端数据鸠合,与此同时,可以自动建立相干的Model实例,并挪用组织要领

元素反复推断

Collection会依据ModelidAttribute指定的唯一键,来推断元素是不是反复,默许状况下唯一键是id,可以重写idAttribute来掩盖。当元素反复的时刻,可以遴选是抛弃反复元素,照样兼并两种元素,默许是抛弃的

模子转化

偶然从REST接口获得的数据并不能完整满足界面的处置惩罚需求,可以经由过程Model.parse或许Collection.parse要领,在实例化Backbone对象前,对数据举行预处置惩罚。大体上,Model.parse用来对返回的单个对象举行属性的处置惩罚,而Collection.parse用来对返回的鸠合举行处置惩罚,通常是过滤掉不必要的数据。比方:

//只遴选type=1的book
var Books = Backbone.Collection.extend({
    parse:function(models,options){
        return _.filter(models , function(model){
            return model.type == 1;
        })
    }
})


//为Book对象增加url属性,以便衬着
var Book = Backbone.Model.extend({
    parse: function(model,options){
        return _.extend(model,{ url : '/books/' + model.id });
    }
})

经由过程Collection的fetch,自动实例化的Model,其parse也会被挪用。

模子的默许值

Model可以经由过程设置defaults属性来设置默许值,这很有效。因为,无论是模子照样鸠合,fetch数据都是异步的,而每每视图的衬着确切很能够在数据到来前就举行了,假如没有默许值的话,一些运用了模板引擎的视图,在衬着的时刻能够会失足。比方underscore自带的视图引擎,因为运用with(){}语法,会因为对象缺少属性而报错。

视图的el

Backbone的视图对象非常简答,关于开发者而言,仅仅体贴一个el属性即可。el属性可以经由过程五种门路给出,优先级从高到低:

  1. 实例化View的时刻,通报el
  2. 在类中声明el
  3. 实例化View的时刻传入tagName
  4. 在类中声明tagName
  5. 以上都没有的状况下运用默许的'div'

终究怎样遴选,取决于以下几点:

  • 平常而言,假如模块是公用模块,在类中不供应el,而是让外部在实例化的时刻传入,如许可以坚持大众的View的独立性,不至于依靠已存在的DOM元素
  • tagName平常关于自成体系的View有效,比方table中的某行tr,ul中的某个li
  • 有些DOM事宜必须在html存在的状况下才绑定胜利,比方blur,关于这类View,只能遴选已存在的html

视图类另有几个属性可以导出,由外部初始化,它们是:

// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

内存走漏

事宜机制可以很好的带来代码保护的方便,然则因为事宜绑定会使对象之间的援用变得复杂和紊乱,轻易形成内存走漏。下面的写法就会形成内存走漏:

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):

《Backbone运用总结》

之所以用child过滤,因为我们的类继续自Backbone的范例,而继续运用了重写原型的要领,Backbone在继续时,运用的变量名为child,末了,child被返回出来了

点击排序后,再次抓取快照,可以看到实例个数变成了14个,这是因为,在render过程当中,又建立了4个新的TaskView,而之前的4个TaskView并没有开释(之所以是4个是因为纪录的条数是4)

《Backbone运用总结》

再次点击排序,再次抓取快照,实例数又增加了4个,变成了18个!

《Backbone运用总结》

那末,为何每次排序后,之前的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,把视图对象能够的被外部的侦听也断开。

    原文作者:P_Chou水冗
    原文地址: https://segmentfault.com/a/1190000000589218
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞