在Backbone.js中处理视图和模型对象
哪种模式/视图实例不需要时最有效的方式?
通常,我将所有的逻辑放在控制器/路由器中。 这是决定的,应该build立什么样的观点,以及应该为他们提供什么样的模式。 通常,有几个处理函数,对应于不同的用户操作或路由,每当处理程序执行时我都会创build新的视图实例。 当然,这应该消除以前存储在视图实例中的任何内容。 但是,在某些情况下,某些视图本身保留了DOM事件处理程序,并且没有正确解除绑定,导致这些实例保持活动状态。 我希望如果有一个正确的方式来销毁视图实例,例如当他们的el(DOM表示)分离,或抛出的DOM
你走在正确的道路上 你应该有一个对象来控制视图的生命周期。 我不喜欢把这个放在我看来。 我喜欢为此创build一个单独的对象。
你需要做的事情是在必要时解除事件。 要做到这一点,在所有视图上创build一个“close”方法是一个好主意,并使用控制所有事物生命周期的对象始终调用close方法。
例如:
function AppController(){ this.showView = function (view){ if (this.currentView){ this.currentView.close(); } this.currentView = view; this.currentView.render(); $("#someElement").html(this.currentView.el); } }
在这一点上,你将设置你的代码只有一个AppController的实例,你总是会调用appController.showView(...)
从你的路由器或任何其他代码,需要在#someElement
部分显示一个视图你的屏幕。
(我有另一个非常简单的骨干应用程序的例子,使用“AppView”(运行应用程序的主要部分的骨干视图),在这里: http : //jsfiddle.net/derickbailey/dHrXv/9/ )
close
方法在默认情况下并不存在于视图中,所以您需要自己为每个视图创build一个。 在close方法中总是有两件事: this.unbind()
和this.remove()
。 除此之外,如果您将视图绑定到任何模型或集合事件,则应该使用close方法解除绑定。
例如:
MyView = Backbone.View.extend({ initialize: function(){ this.model.bind("change", this.modelChanged, this); }, modelChanged: function(){ // ... do stuff here }, close: function(){ this.remove(); this.unbind(); this.model.unbind("change", this.modelChanged); } });
这将正确地清理DOM中的所有事件(通过this.remove()
),视图本身可能引发的所有事件(通过this.unbind()
)以及视图从模型绑定的事件通过this.model.unbind(...)
)。
一个简单的方法是在Backbone.View对象上添加一个自定义的close方法
Backbone.View.prototype.close = function () { this.$el.empty(); this.unbind(); };
使用上面的代码,你可以做到以下几点
var myView = new MyView(); myView.close();
十分简单。
我总是看到这些意见,有时重复使用模型。 确保视图被释放可以是一个痛苦,如果你保持模型。 如果模型没有被正确的绑定,模型可能会保留一个引用。
从Backbone〜0.9.9开始,使用view.listenTo()而不是model.on()绑定模型可以通过控制反转(视图控制绑定而不是模型)来更容易地进行清理。 如果使用view.listenTo()进行绑定,则对view.stopListening()或view.remove()的调用将删除所有绑定。 类似于调用model.off(null,null,this)。
我喜欢用一个closures函数来扩展视图来清理视图,这个函数可以半自动地调用子视图。 子视图必须由父级的属性引用,或者必须将其添加到名为childViews []的父级内的数组中。
这是我使用的closuresfunction..
// fired by the router, signals the destruct event within top view and // recursively collapses all the sub-views that are stored as properties Backbone.View.prototype.close = function () { // calls views closing event handler first, if implemented (optional) if (this.closing) { this.closing(); // this for custom cleanup purposes } // first loop through childViews[] if defined, in collection views // populate an array property ie this.childViews[] = new ControlViews() if (this.childViews) { _.each(this.childViews, function (child) { child.close(); }); } // close all child views that are referenced by property, in model views // add a property for reference ie this.toolbar = new ToolbarView(); for (var prop in this) { if (this[prop] instanceof Backbone.View) { this[prop].close(); } } this.unbind(); this.remove(); // available in Backbone 0.9.9 + when using view.listenTo, // removes model and collection bindings // this.stopListening(); // its automatically called by remove() // remove any model bindings to this view // (pre Backbone 0.9.9 or if using model.on to bind events) // if (this.model) { // this.model.off(null, null, this); // } // remove and collection bindings to this view // (pre Backbone 0.9.9 or if using collection.on to bind events) // if (this.collection) { // this.collection.off(null, null, this); // } }
然后,视图被声明如下。
views.TeamView = Backbone.View.extend({ initialize: function () { // instantiate this array to ensure sub-view destruction on close() this.childViews = []; this.listenTo(this.collection, "add", this.add); this.listenTo(this.collection, "reset", this.reset); // storing sub-view as a property will ensure destruction on close() this.editView = new views.EditView({ model: this.model.edits }); $('#edit', this.el).html(this.editView.render().el); }, add: function (member) { var memberView = new views.MemberView({ model: member }); this.childViews.push(memberView); // add child to array var item = memberView.render().el; this.$el.append(item); }, reset: function () { // manually purge child views upon reset _.each(this.childViews, function (child) { child.close(); }); this.childViews = []; }, // render is called externally and should handle case where collection // was already populated, as is the case if it is recycled render: function () { this.$el.empty(); _.each(this.collection.models, function (member) { this.add(member); }, this); return this; } // fired by a prototype extension closing: function () { // handle other unbinding needs, here } });