首页 > 代码库 > 让你的App飞一会

让你的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 any2    // 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=http://www.mamicode.com/arguments; 5            if(this.beforeRender) data=http://www.mamicode.com/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);

打完收工,希望对你有所启示!

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

让你的App飞一会