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

时间:2014-09-18 14:53:53

标签: javascript backbone.js

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

根据这个article,僵尸是:

  

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

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

话虽如此,根据情况,从何处调用该近距离功能?

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

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

渲染

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

不允许我取消绑定视图,因为它通过执行

按预期工作

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

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

这是代码和工作fiddle

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>

JS

模型和集合

var app = app || {};

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

app.SportsMenCollection = Backbone.Collection.extend({
    model: app.SportsManModel
});

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;
    }
});

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);
    }
});

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)));
    }
});

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);
});

2 个答案:

答案 0 :(得分:7)

就像你发现的那样,Backbone认为自己更像是一个而不是框架 - 它给开发人员留下了很多问题和设计模式。

术语&#34;僵尸视图&#34;用于指定当您认为它们已经死亡时仍然绑定到某个(并因此存活)的视图。通常,对于来自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
}

有些注意事项:

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

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

    然后我在remove上调用this.mainView后,#main-content将从DOM中删除,因此我无法再使用该选择器。通过附加它,我将#main-content作为占位符保留,因此我可以继续添加视图。 这是您在尝试取消绑定SportsManDetailsView然后再渲染时所看到的内容。

  3. 至于你的问题,这个:

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

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

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

    我已更新了您的JSFiddle,以展示我所谈论的一些内容。

答案 1 :(得分:0)

el: '#sportsManDetails',
如果在创建视图的构造函数时未加载jQuery

可能无效。

确保在此行之前加载jQuery

app.SportsManDetailsView = Backbone.View.extend({

您可以使用

进行检查
console.log(jQuery.fn.jquery);`