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

Commit c5fa76b

Browse files
committed
perf($rootScope): make postDigestQueue more efficient
1 parent 707664a commit c5fa76b

File tree

2 files changed

+128
-52
lines changed

2 files changed

+128
-52
lines changed

src/ng/rootScope.js

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

772-
while (asyncQueue.length) {
772+
while (!asyncQueue.isEmpty()) {
773773
try {
774774
asyncTask = asyncQueue.shift();
775775
asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
@@ -778,6 +778,7 @@ function $RootScopeProvider() {
778778
}
779779
lastDirtyWatch = null;
780780
}
781+
asyncQueue.purge();
781782

782783
traverseScopesLoop:
783784
do { // "traverse the scopes" loop
@@ -836,25 +837,26 @@ function $RootScopeProvider() {
836837

837838
// `break traverseScopesLoop;` takes us to here
838839

839-
if ((dirty || asyncQueue.length) && !(ttl--)) {
840+
if ((dirty || !asyncQueue.isEmpty()) && !(ttl--)) {
840841
clearPhase();
841842
throw $rootScopeMinErr('infdig',
842843
'{0} $digest() iterations reached. Aborting!\n' +
843844
'Watchers fired in the last 5 iterations: {1}',
844845
TTL, watchLog);
845846
}
846847

847-
} while (dirty || asyncQueue.length);
848+
} while (dirty || !asyncQueue.isEmpty());
848849

849850
clearPhase();
850851

851-
while (postDigestQueue.length) {
852+
while (!postDigestQueue.isEmpty()) {
852853
try {
853854
postDigestQueue.shift()();
854855
} catch (e) {
855856
$exceptionHandler(e);
856857
}
857858
}
859+
postDigestQueue.purge();
858860
},
859861

860862

@@ -990,11 +992,11 @@ function $RootScopeProvider() {
990992
* @param {(object)=} locals Local variables object, useful for overriding values in scope.
991993
*/
992994
$evalAsync: function(expr, locals) {
993-
// if we are outside of an $digest loop and this is the first time we are scheduling async
995+
// if we are outside of a $digest loop and this is the first time we are scheduling async
994996
// task also schedule async auto-flush
995-
if (!$rootScope.$$phase && !asyncQueue.length) {
997+
if (!$rootScope.$$phase && asyncQueue.isEmpty()) {
996998
$browser.defer(function() {
997-
if (asyncQueue.length) {
999+
if (!asyncQueue.isEmpty()) {
9981000
$rootScope.$digest();
9991001
}
10001002
});
@@ -1305,9 +1307,9 @@ function $RootScopeProvider() {
13051307
var $rootScope = new Scope();
13061308

13071309
//The internal queues. Expose them on the $rootScope for debugging/testing purposes.
1308-
var asyncQueue = $rootScope.$$asyncQueue = [];
1309-
var postDigestQueue = $rootScope.$$postDigestQueue = [];
1310-
var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
1310+
var asyncQueue = $rootScope.$$asyncQueue = new Queue();
1311+
var postDigestQueue = $rootScope.$$postDigestQueue = new Queue();
1312+
var applyAsyncQueue = $rootScope.$$applyAsyncQueue = new Queue();
13111313

13121314
return $rootScope;
13131315

@@ -1347,13 +1349,14 @@ function $RootScopeProvider() {
13471349
function initWatchVal() {}
13481350

13491351
function flushApplyAsync() {
1350-
while (applyAsyncQueue.length) {
1352+
while (!applyAsyncQueue.isEmpty()) {
13511353
try {
13521354
applyAsyncQueue.shift()();
13531355
} catch (e) {
13541356
$exceptionHandler(e);
13551357
}
13561358
}
1359+
applyAsyncQueue.purge();
13571360
applyAsyncId = null;
13581361
}
13591362

@@ -1364,5 +1367,31 @@ function $RootScopeProvider() {
13641367
});
13651368
}
13661369
}
1370+
1371+
// See https://github.com/angular/angular.js/issues/14534
1372+
function Queue() {
1373+
var array = [],
1374+
head = 0;
1375+
this.push = function(item) {
1376+
array.push(item);
1377+
};
1378+
this.shift = function() {
1379+
var item = array[head];
1380+
array[head++] = null;
1381+
return item;
1382+
};
1383+
this.isEmpty = function() {
1384+
return head === array.length;
1385+
};
1386+
this.purge = function() {
1387+
if (head > 0) {
1388+
array = array.slice(head);
1389+
head = 0;
1390+
}
1391+
};
1392+
this.$$getArray = function() {
1393+
return array.slice(head);
1394+
};
1395+
}
13671396
}];
13681397
}

test/ng/rootScopeSpec.js

Lines changed: 88 additions & 41 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;
@@ -1411,7 +1371,7 @@ describe('Scope', function() {
14111371

14121372
expect(childScope.$$asyncQueue).toBe($rootScope.$$asyncQueue);
14131373
expect(isolateScope.$$asyncQueue).toBeUndefined();
1414-
expect($rootScope.$$asyncQueue).toEqual([
1374+
expect($rootScope.$$asyncQueue.$$getArray()).toEqual([
14151375
{scope: $rootScope, expression: $parse('rootExpression'), locals: undefined},
14161376
{scope: childScope, expression: $parse('childExpression'), locals: undefined},
14171377
{scope: isolateScope, expression: $parse('isolateExpression'), locals: undefined}
@@ -1705,6 +1665,93 @@ 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+
expect($rootScope.$$postDigestQueue.isEmpty()).toBe(true);
1703+
signature += 'D';
1704+
});
1705+
1706+
$rootScope.$$postDigest(function() {
1707+
signature += 'C';
1708+
});
1709+
1710+
expect(signature).toBe('');
1711+
$rootScope.$digest();
1712+
expect(signature).toBe('ABCD');
1713+
}));
1714+
1715+
it('should run a $$postDigest call on all child scopes when a parent scope is digested', inject(function($rootScope) {
1716+
var parent = $rootScope.$new(),
1717+
child = parent.$new(),
1718+
count = 0;
1719+
1720+
$rootScope.$$postDigest(function() {
1721+
count++;
1722+
});
1723+
1724+
parent.$$postDigest(function() {
1725+
count++;
1726+
});
1727+
1728+
child.$$postDigest(function() {
1729+
count++;
1730+
});
1731+
1732+
expect(count).toBe(0);
1733+
$rootScope.$digest();
1734+
expect(count).toBe(3);
1735+
}));
1736+
1737+
it('should run a $$postDigest call even if the child scope is isolated', inject(function($rootScope) {
1738+
var parent = $rootScope.$new(),
1739+
child = parent.$new(true),
1740+
signature = '';
1741+
1742+
parent.$$postDigest(function() {
1743+
signature += 'A';
1744+
});
1745+
1746+
child.$$postDigest(function() {
1747+
signature += 'B';
1748+
});
1749+
1750+
expect(signature).toBe('');
1751+
$rootScope.$digest();
1752+
expect(signature).toBe('AB');
1753+
}));
1754+
});
17081755

17091756
describe('events', function() {
17101757

0 commit comments

Comments
 (0)