骨干僵尸的观点和良好做法

Buz*_*zut 7 javascript backbone.js

我对骨干很新,我试图理解僵尸视图的来龙去脉.

根据这篇文章,一个僵尸是:

当我们通过事件将对象绑定在一起但我们不打扰解除绑定它们.只要这些对象绑定在一起,并且我们的应用程序代码中至少有一个引用它们就不会被清理或垃圾收集.由此产生的内存泄漏就像电影中的僵尸一样 - 隐藏在黑暗的角落里,等着跳出去吃午饭.

上面提到的文章建议创建一个对象来管理视图之间的转换,然后实现一个close函数来删除和取消绑定视图.

话虽如此,根据情况,从哪里调用这个关闭功能?

我在父视图的初始化块中添加了一个属性,以保留子视图的跟踪.这样我就可以在我用新的替换之前调用.remove().这是好的做法还是有更好的方法?

我也不明白为什么定义el然后渲染

this.$el.html(this.template(this.model.attributes));

不允许我取消绑定视图,因为它按预期工作

$('#sportsManDetails').html(this.$el.html(this.template(this.model.attributes)));

至于例子,我刚创建了一个简单的应用程序,显示运动员姓名列表,并在点击名称时显示更多详细信息.

这是代码和工作小提琴:

HTML

<script id="nameListTemplate" type="text/template">
    <%= first %> <%= last %>
</script>
<script id="sportsManDetailsTemplate" type="text/template">
    <ul>
        <li><%= first %></li>
        <li><%= last %></li>
        <li><%= age %></li>
        <li><%= sport %></li>
        <li><%= category %></li>
    </ul>
    <button class="test">Test</button>
</script>
<div id="sportsMenName"></div>
<div id="sportsManDetails"></div>
Run Code Online (Sandbox Code Playgroud)

JS

模型和集合

var app = app || {};

app.SportsManModel = Backbone.Model.extend({});

app.SportsMenCollection = Backbone.Collection.extend({
    model: app.SportsManModel
});
Run Code Online (Sandbox Code Playgroud)

NameView

app.NameView = Backbone.View.extend({
    tagName: 'li',
    className: 'sportsMan',
    template: _.template($('#nameListTemplate').html()),

    initialize: function(){
        this.sportsManDetailsView;  
    },

    events: {
        'click': 'showSportsManDetails'
    },

    showSportsManDetails: function(e){
        if (typeof this.sportsManDetailsView !== 'undefined'){
            this.sportsManDetailsView.remove();
        }
        this.sportsManDetailsView = new app.SportsManDetailsView({
            model: this.model
        })  
    },

    render: function(){
        this.$el.append(this.template(this.model.attributes));
        return this;
    }
});
Run Code Online (Sandbox Code Playgroud)

NameListView

app.NameListView = Backbone.View.extend({
    el: '#sportsMenName',

    initialize: function(sportsMen){
        this.collection = new app.SportsMenCollection(sportsMen);
        this.render();
    },

    render: function(){
        this.collection.each(function(sportsMen){
            this.renderContact(sportsMen);
        }, this);
    },

    renderContact: function(sportsMen){
        var nameView = new app.NameView({
            model: sportsMen   
        });
        this.$el.append(nameView.render().el);
    }
});
Run Code Online (Sandbox Code Playgroud)

SportsManDetailsView

app.SportsManDetailsView = Backbone.View.extend({
    // doesn't work if I use el in conjunction with 
    // this.$el.html(this.template(this.model.attributes));
    // el: '#sportsManDetails',
    template: _.template($('#sportsManDetailsTemplate').html()),

    initialize: function(){
        this.render();
    },

    events: {
        'click .test': 'test'
    },

    test: function(){
        alert('test');  
    },

    render: function(){                      
        // that does not work
        //this.$el.html(this.template(this.model.attributes));

        // is this good practice?
        $('#sportsManDetails').html(this.$el.html(this.template(this.model.attributes)));
    }
});
Run Code Online (Sandbox Code Playgroud)

app.js

var sportsMen = [
    {first: 'Quentin', last: 'Tarant', age: '34', sport: 'bike', category: '- 90kg'},
    {first: 'Aymeric', last: 'McArthur', age: '54', sport: 'jetski', category: '200HP'},
    {first: 'Peter', last: 'TheFat', age: '45', sport: 'curling', category: 'dunno'},
    {first: 'Charles', last: 'Martel', age: '21', sport: 'Moto', category: 'MX 250cc'},
];

$(function(){
    new app.NameListView(sportsMen);
});
Run Code Online (Sandbox Code Playgroud)

nck*_*lvn 7

正如您所发现的那样,Backbone认为自己更像是一个库而不是一个框架 - 它给开发人员留下了许多问题和设计模式.

术语"僵尸视图"用于指定当您认为它们已经死亡时仍然绑定到某些东西(并因此活着)的视图.通常,对于来自model.on调用或类似的视图有一个剩余的引用.基本上是一种特定形式的内存泄漏.

要管理视图的生命周期,可以使用父视图,但通常的做法是从路由器执行此操作.路由器用于删除旧视图并在路由事件上实例化新视图.这是我经常做到这一点的片段:

render: function(){
    this.mainView && this.mainView.remove();                    // if there is already a view, remove it
    this.mainView = new SomeOtherKindOfViewDeterminedBySomeEvent(); // instantiate the new view
    this.mainView.render();
    this.mainView.$el.appendTo( '#main-content' );              // append it
}
Run Code Online (Sandbox Code Playgroud)

有些事情需要注意:

  1. 如果没有明确调用remove视图,您的应用程序将容易受到内存泄漏的影响.这是因为View的事件和属性仍然存在于后台.例如,如果删除上面示例的第一行,我将丢失对前者的引用this.mainView,但它的事件仍在使用内存.这将随着时间的推移对您的应用产生影响.
  2. 请注意,我appendTo在最后一行使用.在调用removeView时,它的整个元素以及它的事件都被删除了.如果我只是这样做:

    this.mainView = new SomeOtherKindOfViewDeterminedBySomeEvent({ el: '#main-content' })

    我呼吁再经过removethis.mainView,#main-content将已经从DOM删除,这样我就可以不再使用该选择.通过附加它,我将其保留#main-content为占位符,因此我可以继续添加视图.这是您在尝试取消绑定SportsManDetailsView然后再次渲染时所看到的.

至于你的问题,这个:

$('#sportsManDetails').html(this.$el.html(this.template(this.model.attributes)));

不是好的做法.首先,您已经使用了全局jQuery对象,这会破坏Backbone的封装视图方法.其次,事件在前视图中仍然在DOM中活动,导致内存泄漏.单击"测试"按钮时可以看到这一点 - 每次实例化时都会触发处理函数SportsManDetailsView(第二次,警报消息将显示两次,然后三次,等等)

您应该依靠父视图或路由器来处理此类交互.那,或者保持你SportsManDetailsView#sportsManDetails元素的约束,永远不要删除它.然后当你的click事件发生时NameView,让它的模型触发器触发一个事件.然后你SportsManDetailsView可以在相应的集合中监听事件并相应地重新渲染自己.拥抱Backbone的活动!JavaScript是一种事件驱动的语言,永远不会忘记你有你的炮兵.

我已经更新了你的JSFiddle,以展示我所谈论的一些内容.