Удаление объектов вида и модели в Backbone.js

Каков наиболее эффективный способ распоряжаться экземплярами модели/представления, когда они не нужны?

Обычно я помещаю всю логику в контроллер/маршрутизатор. Это тот, который решает, какие мнения должны быть созданы, и какие модели должны быть предоставлены им. Как правило, существует несколько функций обработчика, соответствующих различным действиям или маршрутам пользователя, где я создаю новые экземпляры представлений каждый раз, когда обработчик выполняется. Конечно, это должно устранить все, что я ранее хранил в экземпляре представления. Однако есть некоторые ситуации, когда в некоторых представлениях хранятся обработчики событий DOM, и они не получают надлежащим образом привязки, что приводит к тому, что эти экземпляры остаются в живых. Я желаю, если бы был правильный способ уничтожить объекты просмотра, когда, например, их эль (представление DOM) отделяется или выкидывается из DOM

Ответ 1

ты на правильном пути. вы должны иметь объект, который управляет жизненным циклом ваших представлений. Мне не нравится это делать на мой взгляд. Мне нравится создавать для этого отдельный объект.

то, что вам нужно сделать, - это отключение событий, когда это необходимо. для этого рекомендуется создать "закрытый" метод для всех ваших представлений и использовать объект, который управляет жизненным циклом всего, чтобы всегда вызывать метод close.

например:


  function AppController(){
    this.showView = function (view){
      if (this.currentView){
        this.currentView.close();
      }
      this.currentView = view;
      this.currentView.render();
      $("#someElement").html(this.currentView.el);
    }
  }

вы должны настроить свой код только для одного экземпляра AppController, и вы всегда вызываете appController.showView(...) с вашего маршрутизатора или любого другого кода, который должен показывать представление в #someElement части ваш экран.

(у меня есть еще один пример очень простого базового приложения, в котором используется "AppView" (базовое представление, которое запускает основную часть приложения): http://jsfiddle.net/derickbailey/dHrXv/9/)

Метод close по умолчанию не существует, поэтому вам нужно создать его самостоятельно для каждого из ваших представлений. Есть две вещи, которые всегда должны быть в методе "close": this.unbind() и this.remove(). в дополнение к этим, если вы привязываете свое мнение к любым событиям модели или коллекции, вы должны развязать их методом close.

например:


  MyView = Backbone.View.extend({
    initialize: function(){
      this.model.bind("change", this.modelChanged, this);
    },

    modelChanged: function(){
      // ... do stuff here
    },

    close: function(){
      this.remove();
      this.unbind();
      this.model.unbind("change", this.modelChanged);
    }
  });

это правильно очистит все события из DOM (через this.remove()), все события, которые может отображаться самим видом (через this.unbind()), и событие, связанное с просмотром модели (через this.model.unbind(...)).

Ответ 2

Способ simpiler заключается в добавлении пользовательского метода закрытия объекта Backbone.View

Backbone.View.prototype.close = function () {
  this.$el.empty();
  this.unbind();
};

Используя приведенный выше код, вы можете сделать следующее

var myView = new MyView();

myView.close();

легкий peasy.

Ответ 3

Я всегда держу взгляды и иногда использую модели. Удостовериться, что взгляды освобождены, может быть боль, если вы держите модели вокруг. Модели могут хранить ссылку на представление, если они не являются несвязанными.

Как и в случае Backbone ~ 0.9.9, привязывающие модели с view.listenTo(), а не model.on(), позволяют упростить очистку посредством инверсии управления (представления управляют привязками в отличие от моделей). Если для bind используется view.listenTo(), то вызов view.stopListening() или view.remove() удалит все привязки. Подобно вызову model.off(null, null, this).

Мне нравится очищать представления, расширяя представление с помощью функции close, которая вызывает под-представления полуавтоматически. В представлениях должны быть указаны свойства родителя, или они должны быть добавлены в массив внутри родительского элемента, называемого childViews [].

Вот функция закрытия, которую я использую.

// fired by the router, signals the destruct event within top view and 
// recursively collapses all the sub-views that are stored as properties
Backbone.View.prototype.close = function () {

    // calls views closing event handler first, if implemented (optional)
    if (this.closing) {
        this.closing();  // this for custom cleanup purposes
    }

    // first loop through childViews[] if defined, in collection views
    //  populate an array property i.e. this.childViews[] = new ControlViews()
    if (this.childViews) {
        _.each(this.childViews, function (child) {
            child.close();
        });
    }

    // close all child views that are referenced by property, in model views
    //  add a property for reference i.e. this.toolbar = new ToolbarView();
    for (var prop in this) {
        if (this[prop] instanceof Backbone.View) {
            this[prop].close();
        }
    }

    this.unbind();
    this.remove();

    // available in Backbone 0.9.9 + when using view.listenTo, 
    //  removes model and collection bindings
    // this.stopListening(); // its automatically called by remove()

    // remove any model bindings to this view 
    //  (pre Backbone 0.9.9 or if using model.on to bind events)
    // if (this.model) {
    //  this.model.off(null, null, this);
    // }

    // remove and collection bindings to this view 
    //  (pre Backbone 0.9.9 or if using collection.on to bind events)
    // if (this.collection) {
    //  this.collection.off(null, null, this);
    // }
}

Затем представление объявляется следующим образом.

views.TeamView = Backbone.View.extend({

    initialize: function () {
        // instantiate this array to ensure sub-view destruction on close()
        this.childViews = [];  

        this.listenTo(this.collection, "add", this.add);
        this.listenTo(this.collection, "reset", this.reset);

        // storing sub-view as a property will ensure destruction on close()
        this.editView = new views.EditView({ model: this.model.edits });
        $('#edit', this.el).html(this.editView.render().el);
    },

    add: function (member) {
        var memberView = new views.MemberView({ model: member });
        this.childViews.push(memberView);    // add child to array

        var item = memberView.render().el;
        this.$el.append(item);
    },

    reset: function () {
        // manually purge child views upon reset
        _.each(this.childViews, function (child) {
            child.close();
        });

        this.childViews = [];
    },

    // render is called externally and should handle case where collection
    // was already populated, as is the case if it is recycled
    render: function () {
        this.$el.empty();

        _.each(this.collection.models, function (member) {
            this.add(member);
        }, this);
        return this;
    }

    // fired by a prototype extension
    closing: function () {
        // handle other unbinding needs, here
    }
});