angular/angular.js — pull request #9006 by jbedard (2014-09-10 05:45:16+00:00)
Title: WIP - only watch the inputs of parsed expressions

This is a newer version of https://github.com/jbedard/angular.js/commit/844d04cb584b77f822b907549fedc5b52d527660 which was discussed as an alternative to https://github.com/angular/angular.js/pull/8942.

The first commit should be good either way and makes `a | noop` about 20% faster in the benchmakrs by avoiding 2 unnecessary wrapper functions (binaryFn and valueFn). I can put this in a separate PR if wanted, but the main change depends on it.

The main change:
* expression inputs are watched instead of the full expression - for example `a + -a * b -b + 5` will watch `a` and `b`
* when expression inputs change the full expression get evaluated, otherwise the previous result is assumed to still be valid
* inputs are currently: variables/properties, field access (`foo().field`), object index (`obj[sub-exp]`) and function calls (and ; separated statements, at least for now)
* this means all other expressions are not watched
* I added (and have not tested) a `externalInput` flag that filters can set if they have some external state, then those filters will be considered inputs

In the expression watching benchmarks (which do not have the issues listed below):
* operators are about 2x faster
* filters are about 3x faster
* object/array literals are about 5x faster
* directly watching input expressions should be unchanged

Issues:
* This only works for primitives today. If any inputs return a non-primitive it is assumed to have changed and executes the full statement. This will hurt performance for expressions with non-primitives inputs.
* All inputs get executed per digest, if any change and the full expression gets executed then those child input expressions will get re-executed. For basic property inputs I think this is fine (although could be an area of improvement) - but for anything with side effects this will be an issue. The issue is not so much the double-calling but the fact that branching expressions should not always call their child expressions. The best example I can think of would be something such as: `$rootScope.$watch('isLoggedIn || loggedOutAction()')`. In this case, with the current implementation, `isLoggedIn` and `loggedOutAction()` would be considered inputs and executed in the watcher.

I might try other solutions to fix the two issues - maybe something simpler that only supports filters/literals for now which will get the best performance gains with the easiest workaround for those 2 issues.