Skip to content
Permalink
Browse files

perf($rootScope): make queues more efficient

By using a pointer to the current start of the queue and only clearing
up the queue storage later, we save lots of time that was spent manipulating
arrays with `slice`

Closes #14545
  • Loading branch information
thorn0 authored and petebacondarwin committed Apr 29, 2016
1 parent 9e28b64 commit cb2f8c0d75bde9ac91f4129e871ff4a8301871f3
Showing with 98 additions and 44 deletions.
  1. +12 −4 src/ng/rootScope.js
  2. +86 −40 test/ng/rootScopeSpec.js
@@ -769,15 +769,19 @@ function $RootScopeProvider() {
dirty = false;
current = target;

while (asyncQueue.length) {
// It's safe for asyncQueuePosition to be a local variable here because this loop can't
// be reentered recursively. Calling $digest from a function passed to $applyAsync would
// lead to a '$digest already in progress' error.
for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) {
try {
asyncTask = asyncQueue.shift();
asyncTask = asyncQueue[asyncQueuePosition];
asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
} catch (e) {
$exceptionHandler(e);
}
lastDirtyWatch = null;
}
asyncQueue.length = 0;

traverseScopesLoop:
do { // "traverse the scopes" loop
@@ -848,13 +852,15 @@ function $RootScopeProvider() {

clearPhase();

while (postDigestQueue.length) {
// postDigestQueuePosition isn't local here because this loop can be reentered recursively.
while (postDigestQueuePosition < postDigestQueue.length) {
try {
postDigestQueue.shift()();
postDigestQueue[postDigestQueuePosition++]();
} catch (e) {
$exceptionHandler(e);
}
}
postDigestQueue.length = postDigestQueuePosition = 0;
},


@@ -1309,6 +1315,8 @@ function $RootScopeProvider() {
var postDigestQueue = $rootScope.$$postDigestQueue = [];
var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];

var postDigestQueuePosition = 0;

return $rootScope;


@@ -1320,46 +1320,6 @@ describe('Scope', function() {
expect(externalWatchCount).toEqual(0);
}));

it('should run a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(),
count = 0;

$rootScope.$$postDigest(function() {
count++;
});

parent.$$postDigest(function() {
count++;
});

child.$$postDigest(function() {
count++;
});

expect(count).toBe(0);
$rootScope.$digest();
expect(count).toBe(3);
}));

it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(true),
signature = '';

parent.$$postDigest(function() {
signature += 'A';
});

child.$$postDigest(function() {
signature += 'B';
});

expect(signature).toBe('');
$rootScope.$digest();
expect(signature).toBe('AB');
}));

it('should cause a $digest rerun', inject(function($rootScope) {
$rootScope.log = '';
$rootScope.value = 0;
@@ -1705,6 +1665,92 @@ describe('Scope', function() {
}));
});

describe('$$postDigest', function() {
it('should process callbacks as a queue (FIFO) when the scope is digested', inject(function($rootScope) {
var signature = '';

$rootScope.$$postDigest(function() {
signature += 'A';
$rootScope.$$postDigest(function() {
signature += 'D';
});
});

$rootScope.$$postDigest(function() {
signature += 'B';
});

$rootScope.$$postDigest(function() {
signature += 'C';
});

expect(signature).toBe('');
$rootScope.$digest();
expect(signature).toBe('ABCD');
}));

it('should support $apply calls nested in $$postDigest callbacks', inject(function($rootScope) {
var signature = '';

$rootScope.$$postDigest(function() {
signature += 'A';
});

$rootScope.$$postDigest(function() {
signature += 'B';
$rootScope.$apply();
signature += 'D';
});

$rootScope.$$postDigest(function() {
signature += 'C';
});

expect(signature).toBe('');
$rootScope.$digest();
expect(signature).toBe('ABCD');
}));

it('should run a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(),
count = 0;

$rootScope.$$postDigest(function() {
count++;
});

parent.$$postDigest(function() {
count++;
});

child.$$postDigest(function() {
count++;
});

expect(count).toBe(0);
$rootScope.$digest();
expect(count).toBe(3);
}));

it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) {
var parent = $rootScope.$new(),
child = parent.$new(true),
signature = '';

parent.$$postDigest(function() {
signature += 'A';
});

child.$$postDigest(function() {
signature += 'B';
});

expect(signature).toBe('');
$rootScope.$digest();
expect(signature).toBe('AB');
}));
});

describe('events', function() {

0 comments on commit cb2f8c0

Please sign in to comment.
You can’t perform that action at this time.