0

Должен ли $watch в Angular удаляться при уничтожении scope?

502

Описание проблемы

Работаю над проектом, в котором мы обнаружили серьезные утечки памяти из-за того, что не очищали подписки на широковещательные сообщения в уничтоженных scopes. Мы исправили эту проблему следующим образом:

var onFooEventBroadcast = $rootScope.$on('fooEvent', doSomething);

scope.$on('$destroy', function() {
    // Удаляем подписку на широковещательное сообщение при уничтожении scope
    onFooEventBroadcast();
});

Вопрос: следует ли применять такую же практику для watches? Пример кода ниже:

var onFooChanged = scope.$watch('foo', doSomething);

scope.$on('$destroy', function() {
    // Останавливаем отслеживание при уничтожении scope
    onFooChanged();
});

Нужна ваша помощь: правильно ли я понимаю, что необходимо очищать watches аналогичным образом для предотвращения утечек памяти?

1 ответ(ов)

0

Нет, вам не нужно вручную удалять $$watchers, так как они будут эффективно удалены, как только область (scope) будет уничтожена.

Из исходного кода Angular (v1.2.21), метод $destroy для Scope выглядит следующим образом:

$destroy: function() {
    ...
    if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
    if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
    if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
    if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
    ...
    this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
    ...

Таким образом, массив $$watchers очищается (и область удаляется из иерархии областей).

Удаление наблюдателя (watcher) из массива — это все, что делает функция отмены регистрации:

$watch: function(watchExp, listener, objectEquality) {
    ...
    return function deregisterWatch() {
        arrayRemove(array, watcher);
        lastDirtyWatch = null;
    };
}

Поэтому нет смысла вручную отменять регистрацию $$watchers.


Однако вам все же следует отменять регистрацию слушателей событий, как вы правильно упомянули в своем посте!

Замечание: Вам нужно отменять регистрацию слушателей, зарегистрированных на других областях. Нет необходимости отменять регистрацию слушателей, зарегистрированных на уничтожаемой области.

Например:

// Вы ДОЛЖНЫ отменить регистрацию этих слушателей
$rootScope.$on(...);
$scope.$parent.$on(...);

// ВЫ НЕ ДОЛЖНЫ отменять регистрацию этого слушателя
$scope.$on(...)

Также убедитесь, что вы отменяете регистрацию любых слушателей событий из элементов, которые переживают уничтожаемую область. Например, если у вас есть директива, которая регистрирует слушатель на родительском узле или на <body>, то вы должны также отменить их регистрацию.


Несколько не связано с оригинальным вопросом, но теперь также существует событие $destroyed, которое вызывается на элементе, который уничтожается, так что вы можете использовать это событие, если это подходит для вашего случая:

link: function postLink(scope, elem) {
  doStuff();
  elem.on('$destroy', cleanUp);
}
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь