【问题标题】:Again on Backbone Zombie Views再次在 Backbone Zombie Views 上
【发布时间】:2014-12-09 18:23:08
【问题描述】:

我正在尝试理解骨干网,目前正在与僵尸观点作斗争。我已经阅读了很多关于这个问题的堆栈溢出帖子,但我仍然无法弄清楚。

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

  1. 创建对象
//定义应用对象 变种应用程序 = { 发泄:{}, 模板:{}, 意见:{}, 路由器:{}, }; //实例化事件聚合器并将其附加到应用程序 app.vent = _.extend({}, Backbone.Events);
  1. 定义两个非常简单的模板(存储在 app.templates 中):第一个有一些虚拟文本和一个按钮(带有和 id 为 'test-begin'),第二个只是虚拟文本

  2. 定义两个视图

app.views.instructions = Backbone.View.extend({ //加载下划线模板 模板:_.template(app.templates.instructions), //实例化时自动调用 初始化:函数(选项){ //将相关函数绑定到视图 _.bindAll(this, 'render', 'testBegin', 'stillAlive', 'beforeClose'); //监听app.vent事件 this.listenTo(app.vent, 'still:alive', this.stillAlive); }, //将事件绑定到DOM元素 事件:{ '点击#test-begin' : 'testBegin', }, //渲染视图 渲染:函数(){ this.$el.html(this.template()); 返回这个; }, //开始考试 测试开始:函数(){ Backbone.history.navigate('begin', {trigger: true}); }, //还活着 仍然活着:函数(){ console.log('我还活着'); }, //关闭前 关闭前:函数(){ //停止监听 app.vent this.stopListening(app.vent); }, }); //测试视图 app.views.test = Backbone.View.extend({ //加载下划线模板 模板:_.template(app.templates.test), //实例化时自动调用 初始化:函数(选项){ //触发 still:alive 并查看移除的视图是否响应它 app.vent.trigger('still:alive'); //将相关函数绑定到视图 _.bindAll(this, 'render'); }, //渲染视图 渲染:函数(){ this.$el.html(this.template()); 返回这个; }, });
  1. 定义路由器
//基础路由器 app.routers.baseRouter = Backbone.Router.extend({ //路线 路线:{ '': “指示”, “开始”:“开始测试” }, //函数(属于对象控制器) 指令:函数(){baseController.instructions()}, 开始测试:函数(){baseController.beginTest()}, }); //baseRouter控制器 var baseController = { 说明:函数(){ mainApp.viewsManager.rederView(new app.views.instructions()); }, 开始测试:功能(选项){ mainApp.viewsManager.rederView(new app.views.test()); }, };
  1. 定义 mainApp(带有视图切换器)
//定义mainApplication对象 主应用程序 = {}; //管理视图切换 mainApp.viewsManager = { //rootEl rootEl: '#test-container', //关闭当前视图并显示下一个 rederView:功能(视图,rootEl){ //如果没有传递DOM el,则将其设置为默认的RootEl rootEl = rootEl ||这个.rootEl; //关闭当前视图 if (this.currentView) this.currentView.close(); //存储对下一个视图的引用 this.currentView = 视图; //渲染下一个视图 $(rootEl).html(this.currentView.render().el); }, }; //渲染应用程序的第一个视图 mainApp.viewsManager.rederView(new app.views.instructions()); //启动路由器并将其附加到应用程序 mainApp.baseRouter = new app.routers.baseRouter(); //开始骨干网历史 Backbone.history.start({沉默:真 });
  1. 添加关闭功能以通过 Backbone 原型查看
//向Backbone视图原型添加函数(在所有视图中可用) Backbone.View.prototype.close = function () { //调用view beforeClose函数,如果它在视图中定义 if (this.beforeClose) this.beforeClose(); //this.el 从 DOM 中移除 & DOM 元素的事件被清理 this.remove(); //取消绑定视图绑定的任何模型和集合事件 this.stopListening(); //检查视图是否有子视图 if (this.hasOwnProperty('_subViews')) { //遍历当前视图的子视图 _(this._subViews).each(function(child){ //调用子视图的关闭方法 child.close(); }); } };

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

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

【问题讨论】:

    标签: javascript backbone.js


    【解决方案1】:

    长帖,有问题请追问

    Zombie View 只是一个不在 DOM 中的视图,它会监听事件并对事件做出反应——有时这种行为是意料之中的,但通常不是这样。

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


    在您的代码中,您没有僵尸视图,因为您正确地关闭了视图。

    您可以看到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);
    },
    

    如果您从关闭方法中删除此行,您将看到僵尸视图并且应该看到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>

    【讨论】:

    • 您的建议解决了我的问题。非常感谢您的明确解释以及您在撰写回复时所花费的时间。 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    • 2013-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多