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

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

我有一个UserListView和UserRowView在左边,UserDetailView在右边。 事情的工作,但我有一个奇怪的行为。 如果我点击左边的一些用户,然后点击删除其中一个,我得到所有已经显示的用户连续的JavaScript确认框。

它看起来像事件绑定的所有以前显示的意见还没有被删除,这似乎是正常的。 我不应该每次在UserRowView上做一个新的UserDetailView? 我应该保持一个观点,并改变其参考模型? 在创build新视图之前,我应该跟踪当前视图并将其删除吗? 我有点失落,任何想法都会受到欢迎。 谢谢 !

这里是左视图的代码(行显示,点击事件,右视图创build)

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

而右视图的代码(删除button)

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

我最近在博客上写了一些内容,并在我的应用程序中展示了几件事情来处理这些情况:

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

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

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

我首先创build一个BaseView,我所有的视图inheritance。 基本的想法是,我的视图将保持对其所订阅的所有事件的引用,以便当处置视图时,所有这些绑定将自动解除绑定。 以下是我的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; 

每当一个视图需要绑定到一个模型或集合上的事件,我会使用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被添加到Events中,使用上面显示的bindTo和unbindFromAll技术。 而且,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(); 

这是一个常见的情况。 如果每次创build一个新的视图,所有旧的视图仍然会被绑定到所有的事件。 你可以做的一件事就是在视图上创build一个名为detatch的函数:

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

然后,在创build新视图之前,请确保在旧视图上调用detatch

当然,正如你所提到的,你总是可以创build一个“细节”视图,而不会改变它。 您可以绑定到模型上的“更改”事件(从视图)来重新渲染自己。 将此添加到您的初始化程序中:

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

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

当然,要做到这一点,需要一个共同的模型,View项目引用以及更高级别的列表视图和细节视图。

希望这可以帮助!

要修复绑定多次的事件,

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

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

我认为大多数人开始与骨干会创build视图,如你的代码:

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

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

我认为清理代码的最佳时机是在创build新视图之前。 我的解决scheme是创build一个帮手来做这个清理:

 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创build视图将有助于清理任何现有的视图,而不必调用view.dispose()。 你可以从你的代码做一个小的修改

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

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

所以,如果你想重复使用视图而不是不断地创build它,那么取决于你,只要视图是干净的,你不需要担心。 只需将createView更改为reuseView:

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

详细的代码和归属地在https://github.com/thomasdao/Backbone-View-Manager上发布;

一种select是绑定,而不是创build一系列新的观点,然后解除这些观点。 你会做这样的事情:

 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设置为另一个用户(即,更改其属性),则可以使用新属性在视图中触发渲染function。

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

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

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