再次在Backbone Zombie Views上

Pie*_*nna 4 javascript backbone.js

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

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

  1. 创建一个对象

    //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中):第一个有一些虚拟文本和一个按钮(带有和'test-begin'的id),第二个只有虚拟文本

  2. 定义两个视图


    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. 定义路由器

    //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(带有视图切换器)

    //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. 通过Backbone原型添加一个close函数来查看


    //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的消息来监听并响应它(尽管它确实不应该).第一个视图确实收听了这样的消息(在控制台日志中我读到'我还活着),即使它被第二个视图替换了.

你能帮助我吗?非常感谢你.

Cor*_*son 6

很长的帖子,如果您有任何疑问,请询问

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

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


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

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

要切换视图,您要调用:

mainApp.viewsManager.rederView(new app.views.test());
Run Code Online (Sandbox Code Playgroud)

调用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);
    },
};
Run Code Online (Sandbox Code Playgroud)

并更新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);
},
Run Code Online (Sandbox Code Playgroud)

如果你从close方法中删除这一行,你将有一个僵尸视图,应该看到console.log.

//unbind any model and collection events that the view is bound to
this.stopListening(); 
Run Code Online (Sandbox Code Playgroud)


僵尸视图示例

在下面的代码中,我创建了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);
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)