【问题标题】:Backbone model objects.... where are they stored so that the DOM can interact with them?骨干模型对象......它们存储在哪里以便 DOM 可以与它​​们交互?
【发布时间】:2015-04-02 22:49:19
【问题描述】:

我刚刚意识到,当谈到骨干时,我不知道自己到底在做什么。当我试图找出一个有说服力的策略来删除模型上的视图事件侦听器时,我意识到了这一点。然后我问“好吧,既然视图已经渲染到 DOM,那么模型在哪里?”然后我问“我在函数体内创建的这个模型对象如何,因此现在我已经将视图渲染到 DOM 并保持状态,因此超出了范围?” 啊啊啊啊!!!!!!!!!

例如。

视图构造函数

Timeclock.Views.JobNewView = Backbone.View.extend({
  template: JST['jobs/_form'],
  events:{
   'blur #job_form :input':'assignValue'
  },
  initialize: function(options){
    this.listenTo(this.model, 'failed-request', this.failedLocationRequest);
    this.listenTo(this.model, 'updated-location', this.updatedLocation);
    this.listenTo(this.model, 'sync', this.renderJobView); 
    this.listenTo(this.model, 'invalid', this.displayModelErrors);
    this.listenTo($(window), 'hashchange', this.clearListeners);
  },
  render: function(){
    this.$el.html(this.template({attributes: this.model.attributes}));
    this.$el.find('#address_fields').listenForAutoFill();
    return this;
  },
  assignValue: function(e){
    var $field = $(e.currentTarget)
    var attr_name = $field.attr('name');
    var value = $field.val();
    this.model.set(attr_name, value);
  }...
});

函数渲染视图到 DOM

renderCollaboratingView: function(e){
  var job = this.model;
  var $row = $(e.currentTarget);
  job.set({customer_id: $row.data('id')});
  var model_view = new this.ViewConstructor({model: job});
  $container.html(model_view.render().el);
}

那么我传递给视图对象的模型如何持久化,以便 DOM 交互可以在底层模型对象上设置属性值?

我知道主干视图只是以声明方式编写 DOM 侦听器的包装器,但在上面的示例中,DOM 事件如何作用于底层模型对象?当 renderCollaboratingView() 函数退出后,我传递给视图的模型如何仍在交互?

我可以想到两种方法:

1) 模型对象通过 jquery 对象绑定到 DOM。我在视图中声明的所有事件侦听器都知道底层模型对象在 jquery 对象上的位置(“模型”属性?)。

2) Backbone 正在创建一些对象命名空间,视图知道它在哪里存储支持 DOM 的模型和集合。我感觉它是第一名,但谁知道呢。

再一次,我来到这里是因为我试图理解为什么我需要删除我首先传递到视图中的模型上的侦听器。如果主干视图实际上只是 jquery 对象,那么当支持 jquery 对象的元素从 DOM 中删除时,jquery 侦听器是否不会从 DOM 元素中删除?如果我不打算完全破坏视图并保存以供以后使用,我是否只需要删除侦听器?

我们将不胜感激任何可以提供的帮助。面临生存危机。

谢谢

【问题讨论】:

  • 您使用的其他语言是什么?与c不同,c++在js上有垃圾收集,当没有人指向它们时会被收集。

标签: javascript jquery backbone.js backbone-views backbone-events


【解决方案1】:

那么我传递给视图对象的模型如何持久化,以便 DOM 交互可以在底层模型对象上设置属性值?

主干模型和视图只是存在于页面范围内的内存中的 Javascript 对象(与任何其他 Javascript 一样)。如果你这样做......

var name = 'Peter';
var person = new Backbone.Model({ name: 'Peter' });
var view = new Backbone.View({ model: person } );

... 那么namepersonview 都只是内存中的对象。它们与 jQuery 无关;它们与 DOM 无关。如果你实现render(),View 恰好能够创建 DOM 元素,但即便如此,这些元素也根本不需要附加到页面的实时 DOM。

... DOM 事件如何作用于上例中的底层模型对象?当 renderCollaboratingView() 函数退出后,我传递给视图的模型如何仍在与之交互?

根据您显示的代码,模型并未直接交互。你的events 哈希...

events:{
    'blur #job_form :input':'assignValue'
},

...确实说过,任何时候blur 事件在job_form 元素中发生,它都会调用视图上的一个名为assignValue 的方法。该方法可能会与模型交互(它可能会,对吗?),但 DOM 事件根本不会直接导致与模型的交互。

如果主干视图真的只是 jquery 对象,那么当支持 jquery 对象的元素从 DOM 中删除时,jquery 侦听器是否不会从 DOM 元素中删除?

Backbone 的监听器与 jQuery 监听器完全不同。他们监听以 Backbone 为中心的事件。 See here 获取 Backbone 组件触发的内置事件列表。 View 的事件哈希是一个很好的约定,用于监听 DOM 事件;基本上是a nice wrapper 围绕事件委托的jQuery 概念。

如果我不打算完全破坏视图并将其保存以供以后使用,我是否只需要删除侦听器?

如果您不移除侦听器,则无论何时发生相关事件,它们都会继续运行,而不管侦听组件是否正在更改页面。假设你有一个 Backbone.View 做了这样的事情:

var MyView = Backbone.View.extend({
    // ...
    events: {
        // Don't do this!
        'click': '_onClick'
    },
    // ...

    _onClick: function() {
         this.$el.append('Clicked!');
    }
 });

只要页面上发生任何click DOM 事件,此视图就会将文本Clicked! 附加到其内部DOM 元素中。当视图附加到页面的 DOM 时,每次点击都会出现Clicked!。当视图从 DOM 中移除时,该函数仍然在每次点击时都会运行...但是由于视图的内部根元素没有附加到任何东西上,因此该函数将不起作用。

这是一种内存泄漏,因为MyView 的任何实例都将被垃圾收集器清除。但特别邪恶的副作用是它使用 CPU 时间来做一些完全没有价值的事情。现在想象一下事件监听器是否做了任何重要的事情。页面的性能会受到影响。

【讨论】:

  • 嗨,彼得,感谢您的回复。我在我的问题中添加了 assignValue() 函数。在该函数中,最后一行调用this.model.set(attr_name, value);,它将在支持视图对象的基础模型上设置属性值。该对象存储在哪里?我知道我实例化了视图对象并传入模型对象,因此我可以访问模型的状态,然后我可以使用它来设置视图中的值。但是,一旦我将 HTML 渲染到 DOM,那个模型对象会去哪里呢?每次调用该函数时,我的 assignValue 函数都可以访问它。
  • 视图对象中的events 将通过事件委托侦听器将视图对象“附加”到视图的el(一个DOM 节点)。这些事件将视图锚定到页面,这为您提供了对模型和集合的引用,因此它们不会被 GC 处理。模型像其他任何东西一样存储在 JavaScript 环境中。
  • 一旦 MyView 不是 dom 的一部分,MyView 上的 click 是否会停止工作?这是为了听点击 myView 这是不可能的,因为它不再是视图的一部分。
  • 一旦 MyView 不是 dom 的一部分,是不是点击 MyView 停止工作?它是用于收听 myView 的点击,这是不可能的,因为它不再是 dom 的一部分——更正。
  • @muistooshort 我认为你在我所问的问题上是正确的(我可能问得非常糟糕)。那么将视图对象固定到 dom 的粘合剂是我在视图构造函数中声明的 events 吗?这对模型对象和视图对象是否也适用......视图通过在模型对象上设置事件侦听器来引用底层模型?即this.listenTo(this.model, 'sync', this.renderJobView);
【解决方案2】:

JavaScript 具有垃圾收集功能。对象不会被破坏,然后它们就会超出范围。当对象X 发现没有人引用(或指向)X 时,它会被运行时系统收集垃圾。

Backbone View 也是一个对象。一个对象可以存储对另一个对象的引用。

在您的renderCollaboratingView 中,您写道:

  var model_view = new this.ViewConstructor({model: job});

这个model_view 是你视图的对象。您传递了您的 job,这是您从中获得的模型:

 renderCollaboratingView: function(e){
    var job = this.model; 
    ....
 }

您可以在主干注释代码中查看这一行:BackBone View Options。 (我建议您在阅读完答案后查看链接)

该行是:

var viewOptions = ['model', 'collection', 'el', 'id', 
                   'attributes', 'className', 'tagName', 'events'];

然后Backbone View被定义为:BackBone View

它是:

 var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    options || (options = {});
    _.extend(this, _.pick(options, viewOptions));
    this._ensureElement();
    this.initialize.apply(this, arguments);
  };

看线:

 _.extend(this, _.pick(options, viewOptions));

和你的代码:

 var model_view = new this.ViewConstructor({model: job});

那么我传递给视图对象的模型如何持久化,以便 DOM 交互可以在底层模型对象上设置属性值?

如果您合并点:您将model 传递给您的视图。您也可以在viewOptions 中传递其他类似'collection', 'el', 'id', ... 的信息。

他们从您传递的对象{model: job} 和视图对象中的extended 中获得pick

这就是您的视图对象如何引用给定模型的方式。

再一次,我来到这里是因为我试图理解为什么我需要删除我首先传递到视图中的模型上的侦听器。

正如我所说,仅从 DOM 中删除 view-object 并不会转到 destroy 它。您必须删除其他对象(此处为 model)对 view-object 的所有引用。

当你说:

initialize: function(options){
     this.listenTo(this.model, 'failed-request', this.failedLocationRequest);
     ....

在你看来。您告诉model 在模型事件failed-request 上调用您的视图对象的failedLocationRequest。只有当您的model's object 存储对view's object 的引用时,这才有可能。因此,您的视图不会被破坏。

不在 dom 中的视图对象将继续从模型和它们注册的所有其他位置(dom 除外)接收此类事件,并会在后台执行您从未想要的事情。绝对不是你想要的..

简单的建议,请致电removeBackBone View remove

并阅读stopListening

【讨论】: