在我的 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;
}
})
我总是销毁和创建视图,因为随着我的单页应用程序变得越来越大,将未使用的实时视图保留在内存中以便我可以重用它们将变得难以维护。
这是我用来清理视图以避免内存泄漏的技术的简化版本。
我首先创建了一个 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;
每当视图需要绑定到模型或集合上的事件时,我都会使用 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 技术添加到事件中。此外,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();
我最近写了一篇博客,并展示了我在我的应用程序中为处理这些场景所做的几件事:
delete view
?
这是一种常见的情况。如果您每次都创建一个新视图,所有旧视图仍将绑定到所有事件。您可以做的一件事是在您的视图上创建一个名为 detatch
的函数:
detatch: function() {
$(this.el).unbind();
this.model.unbind();
然后,在创建新视图之前,请确保在旧视图上调用 detatch
。
当然,正如您所提到的,您始终可以创建一个“详细”视图并且永远不会更改它。您可以绑定到模型上的“更改”事件(从视图中)以重新渲染自己。将此添加到您的初始化程序中:
this.model.bind('change', this.render)
这样做会导致每次对模型进行更改时,详细信息窗格都会重新渲染。您可以通过查看单个属性来获得更精细的粒度:“change:propName”。
当然,这样做需要一个项目视图引用的通用模型以及更高级别的列表视图和详细信息视图。
希望这可以帮助!
this.model.unbind()
对我来说是错误的,因为它取消了该模型的所有事件,包括与同一用户的其他视图有关的事件。此外,为了调用 detach
函数,我需要保持对视图的静态引用,我非常不喜欢它。我怀疑还有什么我不明白的...
要修复多次绑定事件,
$("#my_app_container").unbind()
//Instantiate your views here
在从路由实例化新视图之前使用上述行,解决了我遇到的僵尸视图问题。
我认为大多数人从 Backbone 开始会在您的代码中创建视图:
var view = new UserDetailView({model:this.model});
这段代码创建了僵尸视图,因为我们可能会不断地创建新视图而不清理现有视图。但是,为您的应用程序中的所有主干视图调用 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
一种替代方法是绑定,而不是创建一系列新视图然后解除绑定这些视图。您可以通过以下方式完成此操作:
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,这将设置为用户模型。这样,如果您将 myViewModel 设置为另一个用户(即更改其属性),那么它可能会在具有新属性的视图中触发渲染函数。
一个问题是这会破坏与原始模型的链接。您可以通过使用集合对象或将用户模型设置为视图模型的属性来解决此问题。然后,这将在视图中以 myview.model.get("model") 的形式访问。
使用此方法从内存中清除子视图和当前视图。
//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);
}
});
不定期副业成功案例分享