让你的App飞一会
在基于Backbone的单页应用中,我们可能会有下面这些疑问:
1 如果多次访问同一个页面(hash)时,被多次实例化的视图所占内存释放了么?
2 当你关闭视图后,是不是会发现,它上面的事件还没有移除掉?
3 你是不是在为进一个新页面之前的清理工作而懊恼呢?
因为Backbone是事件驱动的,在Backbone App 应用的许多地方你都能看到事件的身影:
在View中,通过events属性来配置事件的注册:
1 MyView = Backbone.View.extend({ 2 events: { 3 "click #someButton": "doThat", 4 "change #someInput": "changeIt" 5 }, 6 doThat: function(){ ... }, 7 changeIt: function(){ ... } 8 });
我们会监听model的变化来渲染页面:
1 MyView = Backbone.View.extend({ 2 initialize: function(){ 3 this.listenTo(this.model, this.render, this); 4 }, 5 render: function(){ ... } 6 });
我们还可以用全局/应用级的事件对象,来触发页面渲染:
1 MyView = Backbone.View.extend({ 2 initialize: function(){ 3 this.options.vent.bind("app:event", this.render, this); 4 }, 5 render: function() { ... } 6 }); 7 var vent = new _.extend({}, Backbone.Events); 8 new MyView({vent: vent}); 9 vent.trigger("app:event");
事件在给我们带好处的同时,也有其负面影响。通过事件,多个对象会被绑在一起,它们之间也就有了引用关系。而这种引用关系如果不被清除,对象所占内存很可能不会被回收,引发内存泄露。
很常见的场景:
1 MyRouter = Backbone.Router.extend({ 2 routes: { 3 "": "home", 4 "post/:id": "showPost" 5 }, 6 7 home: function(){ 8 var homeView = new HomeView(); 9 $("#mainContent").html(homeView.render().el); 10 }, 11 12 showPost: function(id){ 13 var post = posts.get(id); 14 var postView = new PostView({model: post}); 15 $("#mainContent").html(postView.render().el); 16 } 17 });
如你所想,页面运行是没问题的。但你想过,当进入PostView视图时,HomeView视图上的事件有被清理过吗?它的实例会被回收吗?
我们来简单构造一个类,它专门负责视图上的清理工作:
1 function AppView(){ 2 3 this.showView(view) { 4 if (this.currentView){ 5 this.currentView.close(); 6 } 7 8 this.currentView = view; 9 this.currentView.render(); 10 11 $("#mainContent").html(this.currentView.el); 12 } 13 14 }
那之前的代码可优化如下:
1 MyRouter = Backbone.Router.extend({ 2 routes: { 3 "": "home", 4 "post/:id": "showPost" 5 }, 6 7 initialize: function(options){ 8 this.appView = options.appView; 9 }, 10 11 home: function(){ 12 var homeView = new HomeView(); 13 this.appView.showView(homeView); 14 }, 15 16 showPost: function(id){ 17 var post = posts.get(id); 18 var postView = new PostView({model: post}); 19 this.appView.showView(postView); 20 } 21 });
关闭视图
上面的例子中,当移除一个视图时,都会调用一个close方法,但Backbone.View是没有这个方法的,先讨论下,close方法要具备什么样的功能呢?
1 移除DOM元素上面的事件;
2 移除视图上面的事件;
3 从DOM树中移除相关HTML;
所以,我想close方法大概是这么个样子:
1 Backbone.View.prototype.close = function(){ 2 this.remove(); 3 this.off(); 4 }
首先我们这里调用Backbone.View的remove方法:
1 // Remove this view by taking the element out of the DOM, and removing any 2 // applicable Backbone.Events listeners. 3 remove: function () { 4 this.$el.remove(); 5 this.stopListening(); 6 return this; 7 }
“this.$el.remove()” 从DOM树中移除HTML,“this.stopListening()” 移除视图监听的其他对象上面的事件,” this.off()”移除视图自身的事件,但是看了zepto的remove方法:
1 remove: function(){ 2 return this.each(function(){ 3 if (this.parentNode != null) 4 this.parentNode.removeChild(this) 5 }) 6 },
对于视图里通过”events”来配置注册的事件是否被移除我持怀疑态度,这个暂不作讨论。
设计视图
1 Jass.extend = Backbone.View.extend; 2 Jass.View = Backbone.View.extend({ 3 initialize: function () { 4 var data=arguments; 5 if(this.beforeRender) data=this.beforeRender.apply(this,arguments)||{}; 6 this.render(this.tpl(data)); 7 if(this.afterRender) this.afterRender.call(this,data); 8 }, 9 close: function () { 10 if(this.beforeClose) this.beforeClose(); 11 this.remove(); 12 this.off(); 13 if(this.onClosed) this.onClosed(); 14 //this.trigger(‘close‘); 15 //delete this; 16 }, 17 render: function (el) { 18 this.$el.html(el); 19 }, 20 });
在视图的渲染时,渲染前调用beforeRender,渲染后调用afterRender,可看成其生命周期的两个不同阶段,其中this.tpl= _.template(html模板)。
在close方法中,也有调用beforeClose和onClosed,做你想做的事情吧。
清道夫说,没有我,你们都是瞎忙活:
1 Jass.region = function (node) { 2 this.node = $(node); 3 } 4 Jass.region.prototype.show = function (view) { 5 var node = this.node; 6 var currentView = this.currentView; 7 if (!view.el) { 8 var err = new Error(‘View Must Extend from Jass.View‘); 9 err.name = ‘NoError‘; 10 throw err; 11 } 12 if (view != currentView) { 13 if (currentView) { 14 currentView.close(); 15 delete this.currentView; 16 } 17 } 18 node.html(view.el); 19 if(view.onShow) view.onShow(); 20 view.trigger(‘render‘,view); 21 this.currentView = view; 22 return view; 23 }
在框架里,先做些事情:
1 Jass.Application = function () { 2 this.vent = _.extend({}, Backbone.Events); 3 this._initCallbacks = new $.Callbacks(); 4 }; 5 6 App = _.extend(new Jass.Application,{ 7 "header": new Jass.region("#header"), 8 "body": new Jass.region("#main"), 9 });
然后在我们的App里会这样用:
1 var head=new headView(); 2 App.header.show(head); 3 4 var body=new homeView(); 5 App.body.show(body);
打完收工,希望对你有所启示!
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。