再次在Backbone Zombie Views上

时间:2014-10-14 20:21:04

标签: javascript backbone.js

我正在努力了解骨干,目前正在与僵尸观点进行斗争。我已经阅读了很多关于此事的堆栈溢出帖子,但我仍然无法弄明白。

为简单起见,我设置了两个需要切换的视图(没有数据)。 到目前为止我所做的是:

  1. 创建对象
  2. 
        //define application object
        var app = {
          vent: {},
          templates: {},
          views: {},
          routers: {},
        };
    
        //instantiate event aggregator and attach it to app
        app.vent = _.extend({}, Backbone.Events);
    
    
    1. 定义两个非常简单的模板(存储到app.templates中):第一个模板有一些虚拟文本和一个按钮(其中和id为' test-begin'),第二个只是虚拟文本

    2. 定义两个视图

    3. 
          app.views.instructions = Backbone.View.extend({
      
              //load underscore template
              template: _.template(app.templates.instructions),
      
              //automatically called upon instantiation
              initialize: function(options) {
      
                  //bind relevant fucntions to the view
                  _.bindAll(this, 'render', 'testBegin', 'stillAlive', 'beforeClose');
      
                  //listen to app.vent event 
                  this.listenTo(app.vent, 'still:alive', this.stillAlive);
      
              },
      
              //bind events to DOM elements
              events: {
                  'click #test-begin' : 'testBegin',
              },
      
              //render view
              render: function() {
                  this.$el.html(this.template());
                  return this;
              },
      
              //begin test
              testBegin: function() {
                  Backbone.history.navigate('begin', {trigger: true});
              },
      
              //still alive
              stillAlive: function() {
                  console.log('I am still alive');
              },
      
              //before closing
              beforeClose: function() {
                  //stop listening to app.vent
                  this.stopListening(app.vent);
              },
      
          });
      
          //test view
          app.views.test = Backbone.View.extend({
      
              //load underscore template
              template: _.template(app.templates.test),
      
              //automatically called upon instantiation
              initialize: function(options) {
      
                  //trigger still:alive and see if removed view responds to it
                  app.vent.trigger('still:alive');
      
                  //bind relevant fucntions to the view
                  _.bindAll(this, 'render');
      
              },
      
              //render view
              render: function() {
                  this.$el.html(this.template());
                  return this;
              },
          });
      
      
      1. 定义路由器
      2. 
            //base router
            app.routers.baseRouter = Backbone.Router.extend({
        
                //routes    
                routes: {
                    '': "instructions",
                    'begin': "beginTest"
                },
        
                //functions (belong to object controller)
                instructions: function() {baseController.instructions()},
                beginTest   : function() {baseController.beginTest()},
            });
        
            //baseRouter controller
            var baseController = {
        
                instructions: function() {
                   mainApp.viewsManager.rederView(new app.views.instructions());
        
                },
        
                beginTest: function(options) {
                   mainApp.viewsManager.rederView(new app.views.test());
                },
            };
        
        
        1. 定义mainApp(带有视图切换器)
        2. 
              //define mainApplication object
              mainApp = {};
          
                  //manages views switching  
                  mainApp.viewsManager = {  
          
                      //rootEl
                      rootEl: '#test-container',
          
                      //close current view and show next one
                      rederView : function(view, rootEl) {   
          
                          //if DOM el isn't passed, set it to the default RootEl
                          rootEl = rootEl || this.rootEl;
          
                          //close current view
                          if (this.currentView) this.currentView.close();
          
                          //store reference to next view
                          this.currentView = view;
          
                          //render next view
                          $(rootEl).html(this.currentView.render().el);
                      },
                  };
          
                  //render first view of app
                  mainApp.viewsManager.rederView(new app.views.instructions());
          
                  //initiate router and attach it to app 
                  mainApp.baseRouter = new app.routers.baseRouter();
          
                  //start Backbone history
                  Backbone.history.start({silent: true
          
          });
          
          1. 添加close函数以通过Backbone原型进行查看
          2. 
            
                //add function to Backbone view prototype (available in all views)
                    Backbone.View.prototype.close = function () {
            
                        //call view beforeClose function if it is defined in the view
                        if (this.beforeClose) this.beforeClose();
            
                        //this.el is removed from the DOM & DOM element's events are cleaned up
                        this.remove();
            
                        //unbind any model and collection events that the view is bound to
                        this.stopListening(); 
            
                        //check whether view has subviews
                        if (this.hasOwnProperty('_subViews')) {
            
                            //loop thorugh current view's subviews
                            _(this._subViews).each(function(child){
            
                                //invoke subview's close method
                                child.close();
                            });
                        }
                    };
            
            

            因此,为了检查僵尸视图,第二个视图触发和事件(仍然是:alive)第一个视图通过发送到console.log的消息来监听并响应它(尽管它真的不应该&#39 ; T)。 第一个视图确实收听了这样的消息(在控制台日志中我读到了#39;我还活着),即使它被第二个视图替换了。

            你能帮帮我吗?非常感谢你。

1 个答案:

答案 0 :(得分:6)

长篇大论,如果您有任何疑问,请询问

僵尸视图只是一个不在DOM中的视图,但是监听事件并对事件作出反应 - 有时这种行为是预期的,但通常不是。

如果未正确删除视图的DOM事件处理程序,则不会对视图及其内存中的HTML片段进行垃圾回收。如果Backbone.Event处理程序未正确绑定,您可能会遇到各种不良行为......例如一堆“Zombie”视图在模型上触发AJAX请求。在stopListeninglistenTo之前的旧版Backbone上,此问题非常常见,尤其是如果您在视图之间共享模型。


在您的代码中,您没有Zombie View,因为您正在关闭视图。

您可以看到console.log因为您在关闭第一个视图之前初始化第二个视图(并触发事件still:alive)。

要切换视图,请致电:

mainApp.viewsManager.rederView(new app.views.test());

调用new app.views.test()初始化第二个视图,该视图触发第一个侦听的事件。

如果您将代码更新为以下内容,则不会再看到console.log

//baseRouter controller
var baseController = {

    instructions: function() {
       mainApp.viewsManager.rederView(app.views.instructions);

    },

    beginTest: function(options) {
       mainApp.viewsManager.rederView(app.views.test);
    },
};

并更新rederView

rederView : function(ViewClass, rootEl) {   
    //if DOM el isn't passed, set it to the default RootEl
    rootEl = rootEl || this.rootEl;

    //close current view
    if (this.currentView) this.currentView.close();

    //store reference to next view
    this.currentView = new ViewClass();

    //render next view
    $(rootEl).html(this.currentView.render().el);
},

如果从close方法中删除此行,您将拥有一个僵尸视图,并且应该看到console.log

//unbind any model and collection events that the view is bound to
this.stopListening(); 


僵尸视图示例

在下面的代码中,我创建了100个视图,但只在DOM中显示1。每个视图都包含相同的模型并监听它的change事件。单击视图的<button>元素时,它会更新模型,导致每个视图的模型更改处理程序被执行,调用fetch 100次... 100个AJAX请求!

视图的更改处理程序被调用100次,因为视图关闭方法不会调用this.stopListening(),因此即使从页面中删除视图,它们仍然会监听模型的事件。单击该按钮后,模型将更改,并且所有僵尸视图都会响应,即使它们不在页面上。

var TestView = Backbone.View.extend({
  tagName: 'h1',
  initialize: function(options) {
    this.i = options.i;
    this.listenTo(options.model, 'change', function(model) {
        model.fetch();
    });
  },
  events: {
    'click button': function() {
      this.model.set("show_zombies", Date.now());
    }
  },
  render: function() {
    this.$el.append("<button>Click To Test for Zombies!</button>");
    return this;
  },
  close: function() {
    this.$el.empty(); // empty view html
    // this.$el.off(); // // Whoops! Forgot to unbind Event listeners! (this view won't get garbage collected)
    // this.stopListening() // Whoops! Forgot to unbind Backbone.Event listeners.
  }
});

var model = new (Backbone.Model.extend({
    fetch: function() {
      document.body.innerHTML += "MODEL.FETCH CALLED<br />"
    }
}));

var v;
for (var i = 1; i < 101; i++) {
  if (v) v.close();
  v = new TestView({
    'i': i,
    'model': model
  }).render();

  $('body').html(v.el);
}
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone.js"></script>