Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 51b78f6

Browse files
thorn0petebacondarwin
authored andcommitted
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
1 parent 739100e commit 51b78f6

File tree

2 files changed

+98
-44
lines changed

2 files changed

+98
-44
lines changed

src/ng/rootScope.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,15 +769,19 @@ function $RootScopeProvider() {
769769
dirty = false;
770770
current = target;
771771

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

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

849853
clearPhase();
850854

851-
while (postDigestQueue.length) {
855+
// postDigestQueuePosition isn't local here because this loop can be reentered recursively.
856+
while (postDigestQueuePosition < postDigestQueue.length) {
852857
try {
853-
postDigestQueue.shift()();
858+
postDigestQueue[postDigestQueuePosition++]();
854859
} catch (e) {
855860
$exceptionHandler(e);
856861
}
857862
}
863+
postDigestQueue.length = postDigestQueuePosition = 0;
858864
},
859865

860866

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

1318+
var postDigestQueuePosition = 0;
1319+
13121320
return $rootScope;
13131321

13141322

test/ng/rootScopeSpec.js

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,46 +1320,6 @@ describe('Scope', function() {
13201320
expect(externalWatchCount).toEqual(0);
13211321
}));
13221322

1323-
it('should run a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) {
1324-
var parent = $rootScope.$new(),
1325-
child = parent.$new(),
1326-
count = 0;
1327-
1328-
$rootScope.$$postDigest(function() {
1329-
count++;
1330-
});
1331-
1332-
parent.$$postDigest(function() {
1333-
count++;
1334-
});
1335-
1336-
child.$$postDigest(function() {
1337-
count++;
1338-
});
1339-
1340-
expect(count).toBe(0);
1341-
$rootScope.$digest();
1342-
expect(count).toBe(3);
1343-
}));
1344-
1345-
it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) {
1346-
var parent = $rootScope.$new(),
1347-
child = parent.$new(true),
1348-
signature = '';
1349-
1350-
parent.$$postDigest(function() {
1351-
signature += 'A';
1352-
});
1353-
1354-
child.$$postDigest(function() {
1355-
signature += 'B';
1356-
});
1357-
1358-
expect(signature).toBe('');
1359-
$rootScope.$digest();
1360-
expect(signature).toBe('AB');
1361-
}));
1362-
13631323
it('should cause a $digest rerun', inject(function($rootScope) {
13641324
$rootScope.log = '';
13651325
$rootScope.value = 0;
@@ -1705,6 +1665,92 @@ describe('Scope', function() {
17051665
}));
17061666
});
17071667

1668+
describe('$$postDigest', function() {
1669+
it('should process callbacks as a queue (FIFO) when the scope is digested', inject(function($rootScope) {
1670+
var signature = '';
1671+
1672+
$rootScope.$$postDigest(function() {
1673+
signature += 'A';
1674+
$rootScope.$$postDigest(function() {
1675+
signature += 'D';
1676+
});
1677+
});
1678+
1679+
$rootScope.$$postDigest(function() {
1680+
signature += 'B';
1681+
});
1682+
1683+
$rootScope.$$postDigest(function() {
1684+
signature += 'C';
1685+
});
1686+
1687+
expect(signature).toBe('');
1688+
$rootScope.$digest();
1689+
expect(signature).toBe('ABCD');
1690+
}));
1691+
1692+
it('should support $apply calls nested in $$postDigest callbacks', inject(function($rootScope) {
1693+
var signature = '';
1694+
1695+
$rootScope.$$postDigest(function() {
1696+
signature += 'A';
1697+
});
1698+
1699+
$rootScope.$$postDigest(function() {
1700+
signature += 'B';
1701+
$rootScope.$apply();
1702+
signature += 'D';
1703+
});
1704+
1705+
$rootScope.$$postDigest(function() {
1706+
signature += 'C';
1707+
});
1708+
1709+
expect(signature).toBe('');
1710+
$rootScope.$digest();
1711+
expect(signature).toBe('ABCD');
1712+
}));
1713+
1714+
it('should run a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) {
1715+
var parent = $rootScope.$new(),
1716+
child = parent.$new(),
1717+
count = 0;
1718+
1719+
$rootScope.$$postDigest(function() {
1720+
count++;
1721+
});
1722+
1723+
parent.$$postDigest(function() {
1724+
count++;
1725+
});
1726+
1727+
child.$$postDigest(function() {
1728+
count++;
1729+
});
1730+
1731+
expect(count).toBe(0);
1732+
$rootScope.$digest();
1733+
expect(count).toBe(3);
1734+
}));
1735+
1736+
it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) {
1737+
var parent = $rootScope.$new(),
1738+
child = parent.$new(true),
1739+
signature = '';
1740+
1741+
parent.$$postDigest(function() {
1742+
signature += 'A';
1743+
});
1744+
1745+
child.$$postDigest(function() {
1746+
signature += 'B';
1747+
});
1748+
1749+
expect(signature).toBe('');
1750+
$rootScope.$digest();
1751+
expect(signature).toBe('AB');
1752+
}));
1753+
});
17081754

17091755
describe('events', function() {
17101756

0 commit comments

Comments
 (0)