Angular выражения в $watch trigger дважды

Почему этот $watch срабатывает дважды, когда простое сравнение передается как watchExpression?

$scope.foo = 0;  // simple counter

$scope.$watch('foo > 4', function() {
  console.log("foo is greater than 4: ", $scope.foo);
});

Слушатель запускается при загрузке страницы, когда foo равно 0, затем еще раз (и только один раз), когда значение foo превышает 4.

Почему при загрузке страницы прислушивается пожар? И почему он не продолжает срабатывать, когда foo больше 4?

Я установил простой plunkr, чтобы показать, что происходит: http://plnkr.co/edit/ghYRl9?p=preview

Ответ 1

После повторного чтения Angular $watch docs несколько раз, я думаю, что понимаю, что происходит.

Слушатель вызывается только тогда, когда значение из текущего watchExpression и предыдущего вызова watchExpression не равно

Angular отслеживает значение watchExpression foo > 4 с каждым циклом digest(). Поскольку это оценивалось как false до тех пор, пока foo не было больше 4, слушатель не срабатывал. Аналогично, после того, как foo было больше 4, сравнивались значения Angular. Единственный раз, когда он обнаружил изменение, было, когда оцениваемое выражение пересекло.

Два значения передаются в функцию $watch прослушивателя, новое значение и старое. Запись этих значений показывает, что оценка watchExpression оценивается, а Angular ищет изменение этих значений.

$scope.$watch('foo > 4', function(newVal, oldVal) {
  console.log("foo is greater than 4: ", $scope.foo, oldVal, newVal);
});

// foo is greater than 4:  5 true false

Причина, по которой слушатель вызывается при загрузке страницы, также документирована:

После того, как наблюдатель зарегистрирован в области видимости, listener fn вызывается асинхронно (через $evalAsync) для инициализации наблюдателя. В редких случаях это нежелательно, потому что слушатель вызывается, когда результат watchExpression не изменился. Чтобы обнаружить этот сценарий в listener fn, вы можете сравнить newVal и oldVal. Если эти два значения идентичны (===), то слушатель вызывался из-за инициализации.

Я обновил plunkr со второй $watch, которая внутренне оценивает значение foo:

$scope.$watch('foo', function(newVal, oldVal) {
  if ($scope.foo > 4) {
  console.log("foo is greater than 4: ", $scope.foo, newVal, oldVal);
  }
});

// foo is greater than 4:  5 5 4
// foo is greater than 4:  6 6 5
// foo is greater than 4:  7 7 6