让你的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);

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

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

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。