angular : $eval & $timeout
$digest: function() { var watch, value, last, watchers, length, dirty, ttl = TTL, next, current, target = this, watchLog = [], logIdx, logMsg, asyncTask; beginPhase('$digest'); // Check for changes to browser url that happened in sync before the call to $digest $browser.$$checkUrlChange(); if (this === $rootScope && applyAsyncId !== null) { // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then // cancel the scheduled $apply and flush the queue of expressions to be evaluated. $browser.defer.cancel(applyAsyncId); flushApplyAsync(); } lastDirtyWatch = null; do { // "while dirty" loop dirty = false; current = target; while (asyncQueue.length) { //先跑$eval try { asyncTask = asyncQueue.shift(); asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); } catch (e) { $exceptionHandler(e); } lastDirtyWatch = null; } traverseScopesLoop: do { // "traverse the scopes" loop if ((watchers = current.$$watchers)) { // process our watches length = watchers.length; while (length--) { try { watch = watchers[length]; // Most common watches are on primitives, in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value, null) : value; watch.fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; watchLog[logIdx].push({ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, newVal: value, oldVal: last }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers // have already been tested. dirty = false; break traverseScopesLoop; } } } catch (e) { $exceptionHandler(e); } } } // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $broadcast if (!(next = ((current.$$watchersCount && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next)); // `break traverseScopesLoop;` takes us to here if ((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', TTL, watchLog); } } while (dirty || asyncQueue.length); clearPhase(); while (postDigestQueue.length) {//最后跑$timeout try { postDigestQueue.shift()(); } catch (e) { $exceptionHandler(e); } } },
angular digest 会先跑 $browser.$$checkUrlChange();, 其他的我带过,接着就是check dirty 和 asyncQueue.length。 最后是check postDigestQuese.length