【问题标题】:Backbone zombie views & good practice骨干僵尸观点和良好实践
【发布时间】:2014-09-18 14:53:52
【问题描述】:

我对骨干很陌生,我试图了解僵尸视图的来龙去脉。

僵尸是,根据这个article

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

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

话虽如此,根据情况,从哪里调用关闭函数?

我在父视图的初始化块中添加了一个属性来跟踪子视图。这样我就可以在用新的替换它之前调用 .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
});

名称视图

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

【问题讨论】:

    标签: javascript backbone.js


    【解决方案1】:

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

    术语“僵尸视图”用于指定当您认为它们已死时仍然绑定到某物(因此还活着)的视图。通常有一个来自 model.on call 或类似的视图的剩余引用。基本上是一种特定形式的内存泄漏。

    要管理视图的生命周期,您可以使用父视图,但通常从路由器执行此操作。路由器用于删除旧视图并在路由事件上实例化新视图。这是我经常完成此操作的 sn-p:

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

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

    至于你的问题,这个:

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

    不是好的做法。首先是您使用了全局 jQuery 对象,它打败了 Backbone 封装视图的方法。其次,从以前的视图来看,事件在 DOM 中仍然处于活动状态,从而导致内存泄漏。您可以在单击“测试”按钮时看到这一点 - 每次实例化 SportsManDetailsView 时都会触发处理函数(第二次,警报消息将显示两次,然后显示 3 次,依此类推)

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

    我已经更新了你的JSFiddle 来展示我所说的一些内容。

    【讨论】:

    • 这真是太棒了!你在这件事上启发了我。非常感谢
    • 没问题!你问了一个很好的问题,值得很好的解释。祝您的 Backbone 之旅好运!
    • 我不认为它会完美无缺,但它是游戏的一部分。再次感谢你:)
    • “僵尸视图”是指当您认为它们已死时仍然绑定到某物(因此还活着)的视图。通常有来自model.on 调用或类似的视图的剩余引用。基本上是一种特定形式的内存泄漏。
    • 随时编辑我的帖子以更正“僵尸视图”的定义
    【解决方案2】:
    el: '#sportsManDetails',
    

    如果您在创建视图的构造函数时未加载 jQuery,则可能无法正常工作。

    确保在此行之前加载了jQuery

    app.SportsManDetailsView = Backbone.View.extend({
    

    你可以用

    查看
    console.log(jQuery.fn.jquery);`
    

    【讨论】:

    • 然而,在 app.SportsManDetailsView = Backbone.View.extend({ 输出 2.1.1 之前添加 console.log(jQuery.fn.jquery);,证明 jQuery 加载良好……
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多