Backbone.js:重新填充或重新创建视图?

时间:2011-09-27 10:07:31

标签: javascript templates backbone.js

在我的Web应用程序中,左侧的表中有一个用户列表,右侧有一个用户详细信息窗格。当管理员点击表格中的用户时,其详细信息应显示在右侧。

我左边有一个UserListView和UserRowView,右边有一个UserDetailView。事情很有效,但我有一种奇怪的行为。如果我单击左侧的某些用户,然后单击其中一个用户的删除,我会为所有已显示的用户提供连续的javascript确认框。

看起来所有以前显示的视图的事件绑定都没有被删除,这似乎是正常的。我不应该每次在UserRowView上都做一个新的UserDetailView?我应该维护一个视图并更改其参考模型吗?我应该跟踪当前视图并在创建新视图之前将其删除吗?我有点迷茫,任何想法都会受到欢迎。谢谢!

以下是左视图的代码(行显示,单击事件,右视图创建)

window.UserRowView = Backbone.View.extend({
    tagName : "tr",
    events : {
        "click" : "click",
    },
    render : function() {
        $(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
        return this;
    },
    click : function() {
        var view = new UserDetailView({model:this.model})
        view.render()
    }
})

右视图代码(删除按钮)

window.UserDetailView = Backbone.View.extend({
    el : $("#bbBoxUserDetail"),
    events : {
        "click .delete" : "deleteUser"
    },
    initialize : function() {
        this.model.bind('destroy', function(){this.el.hide()}, this);
    },
    render : function() {
        this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
        this.el.show();
    },
    deleteUser : function() {
        if (confirm("Really delete user " + this.model.get("login") + "?")) 
            this.model.destroy();
        return false;
    }
})

7 个答案:

答案 0 :(得分:136)

我总是销毁和创建视图,因为随着我的单页应用变得越来越大,将未使用的实时视图保留在内存中以便我可以重新使用它们将变得难以维护。

这是我用来清理视图以避免内存泄漏的技术的简化版本。

我首先创建一个我的所有视图都继承自的BaseView。基本思想是我的View将保留对它所订阅的所有事件的引用,以便在处理View时,所有这些绑定将自动解除绑定。这是我的BaseView的示例实现:

var BaseView = function (options) {

    this.bindings = [];
    Backbone.View.apply(this, [options]);
};

_.extend(BaseView.prototype, Backbone.View.prototype, {

    bindTo: function (model, ev, callback) {

        model.bind(ev, callback, this);
        this.bindings.push({ model: model, ev: ev, callback: callback });
    },

    unbindFromAll: function () {
        _.each(this.bindings, function (binding) {
            binding.model.unbind(binding.ev, binding.callback);
        });
        this.bindings = [];
    },

    dispose: function () {
        this.unbindFromAll(); // Will unbind all events this view has bound to
        this.unbind();        // This will unbind all listeners to events from 
                              // this view. This is probably not necessary 
                              // because this view will be garbage collected.
        this.remove(); // Uses the default Backbone.View.remove() method which
                       // removes this.el from the DOM and removes DOM events.
    }

});

BaseView.extend = Backbone.View.extend;

每当View需要绑定到模型或集合上的事件时,我都会使用bindTo方法。例如:

var SampleView = BaseView.extend({

    initialize: function(){
        this.bindTo(this.model, 'change', this.render);
        this.bindTo(this.collection, 'reset', this.doSomething);
    }
});

每当我删除一个视图时,我只需调用dispose方法,它将自动清理所有内容:

var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();

我与正在编写“Backbone.js on Rails”电子书的人分享了这种技巧,我相信这是他们为本书采用的技术。

更新时间:2014-03-24

从Backone 0.9.9开始,listenTo和stopListening使用上面显示的相同bindTo和unbindFromAll技术添加到Events。此外,View.remove会自动调用stopListening,因此绑定和取消绑定就像现在一样简单:

var SampleView = BaseView.extend({

    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
    }
});

var sampleView = new SampleView({model: some_model});
sampleView.remove();

答案 1 :(得分:27)

我最近在博客上发表了这篇文章,并展示了我在应用中处理这些场景的几件事:

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

答案 2 :(得分:8)

这是一种常见情况。如果每次都创建一个新视图,所有旧视图仍将绑定到所有事件。您可以做的一件事是在视图上创建一个名为detatch的函数:

detatch: function() {
   $(this.el).unbind();
   this.model.unbind();

然后,在创建新视图之前,请务必在旧视图上调用detatch

当然,正如您所提到的,您始终可以创建一个“详细信息”视图,而不会更改它。您可以绑定到模型上的“更改”事件(从视图中)以重新渲染自己。将其添加到初始化程序:

this.model.bind('change', this.render)

这样做会导致细节窗格在每次对模型进行更改时重新呈现。您可以通过观察单个属性来获得更精细的粒度:“change:propName”。

当然,这样做需要项目View引用的通用模型以及更高级别的列表视图和详细信息视图。

希望这有帮助!

答案 3 :(得分:6)

多次修复事件绑定,

$("#my_app_container").unbind()
//Instantiate your views here

在从路由实例化新视图之前使用上面的行,解决了我对僵尸视图的问题。

答案 4 :(得分:2)

我认为大多数人从Backbone开始都会在代码中创建视图:

var view = new UserDetailView({model:this.model});

此代码创建僵尸视图,因为我们可能不断创建新视图而不清理现有视图。但是,为应用程序中的所有Backbone视图调用view.dispose()并不方便(特别是如果我们在for循环中创建视图)

我认为放置清理代码的最佳时机是在创建新视图之前。我的解决方案是创建一个帮助程序来执行此清理:

window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        // Cleanup view
        // Remove all of the view's delegated events
        VM.views[name].undelegateEvents();
        // Remove view from the DOM
        VM.views[name].remove();
        // Removes all callbacks on view
        VM.views[name].off();

        if (typeof VM.views[name].close === 'function') {
            VM.views[name].close();
        }
    }
    VM.views[name] = callback();
    return VM.views[name];
}

VM.reuseView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        return VM.views[name];
    }

    VM.views[name] = callback();
    return VM.views[name];
}

使用VM创建视图将有助于清除任何现有视图,而无需调用view.dispose()。您可以从

对代码进行一些小修改
var view = new UserDetailView({model:this.model});

var view = VM.createView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

因此,如果您想重复使用视图而不是不断创建视图,那么只要视图干净,您就不必担心。只需将createView更改为reuseView:

var view = VM.reuseView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

详细的代码和归因信息发布在https://github.com/thomasdao/Backbone-View-Manager

答案 5 :(得分:0)

另一种方法是绑定,而不是创建一系列新视图,然后取消绑定这些视图。你可以做到这样做:

window.User = Backbone.Model.extend({
});

window.MyViewModel = Backbone.Model.extend({
});

window.myView = Backbone.View.extend({
    initialize: function(){
        this.model.on('change', this.alert, this); 
    },
    alert: function(){
        alert("changed"); 
    }
}); 

您可以将myView的模型设置为myViewModel,该模型将设置为User模型。这样,如果将myViewModel设置为另一个用户(即更改其属性),则可以使用新属性在视图中触发渲染功能。

一个问题是这会破坏与原始模型的链接。您可以通过使用集合对象或将用户模型设置为viewmodel的属性来解决此问题。然后,这将在视图中作为myview.model.get(" model")访问。

答案 6 :(得分:0)

使用此方法清除内存中的子视图和当前视图。

//FIRST EXTEND THE BACKBONE VIEW....
//Extending the backbone view...
Backbone.View.prototype.destroy_view = function()
{ 
   //for doing something before closing.....
   if (this.beforeClose) {
       this.beforeClose();
   }
   //For destroying the related child views...
   if (this.destroyChild)
   {
       this.destroyChild();
   }
   this.undelegateEvents();
   $(this.el).removeData().unbind(); 
  //Remove view from DOM
  this.remove();  
  Backbone.View.prototype.remove.call(this);
 }



//Function for destroying the child views...
Backbone.View.prototype.destroyChild  = function(){
   console.info("Closing the child views...");
   //Remember to push the child views of a parent view using this.childViews
   if(this.childViews){
      var len = this.childViews.length;
      for(var i=0; i<len; i++){
         this.childViews[i].destroy_view();
      }
   }//End of if statement
} //End of destroyChild function


//Now extending the Router ..
var Test_Routers = Backbone.Router.extend({

   //Always call this function before calling a route call function...
   closePreviousViews: function() {
       console.log("Closing the pervious in memory views...");
       if (this.currentView)
           this.currentView.destroy_view();
   },

   routes:{
       "test"    :  "testRoute"
   },

   testRoute: function(){
       //Always call this method before calling the route..
       this.closePreviousViews();
       .....
   }


   //Now calling the views...
   $(document).ready(function(e) {
      var Router = new Test_Routers();
      Backbone.history.start({root: "/"}); 
   });


  //Now showing how to push child views in parent views and setting of current views...
  var Test_View = Backbone.View.extend({
       initialize:function(){
          //Now setting the current view..
          Router.currentView = this;
         //If your views contains child views then first initialize...
         this.childViews = [];
         //Now push any child views you create in this parent view. 
         //It will automatically get deleted
         //this.childViews.push(childView);
       }
  });