Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit 75a3f96

Browse files
committed
Optimize $indexFor by caching index locations in a weak map
src/FirebaseArray.js: added a cache of record indices test/unit/firebase.spec.js: Fix broken test case test/mocks/mock.utils.js: unused parameter
1 parent 38e7a36 commit 75a3f96

File tree

3 files changed

+35
-16
lines changed

3 files changed

+35
-16
lines changed

src/FirebaseArray.js

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@
6363
this._promise = readyPromise;
6464
this._destroyFn = destroyFn;
6565

66+
// indexCache is a weak hashmap (a lazy list) of keys to array indices,
67+
// items are not guaranteed to stay up to date in this list (since the list
68+
// can be manually edited without calling the $ methods) and it should
69+
// always be used with skepticism regarding whether it is accurate
70+
// (see $indexFor() below for proper usage)
71+
this._indexCache = {};
72+
6673
// Array.isArray will not work on objects which extend the Array class.
6774
// So instead of extending the Array class, we just return an actual array.
6875
// However, it's still possible to extend FirebaseArray and have the public methods
@@ -175,8 +182,16 @@
175182
*/
176183
$indexFor: function(key) {
177184
var self = this;
178-
// todo optimize and/or cache these? they wouldn't need to be perfect
179-
return this.$list.findIndex(function(rec) { return self._getKey(rec) === key; });
185+
var cache = self._indexCache;
186+
// evaluate whether our key is cached and, if so, whether it is up to date
187+
if( !cache.hasOwnProperty(key) || self.$keyAt(cache[key]) !== key ) {
188+
// update the hashmap
189+
var pos = self.$list.findIndex(function(rec) { return self._getKey(rec) === key; });
190+
if( pos !== -1 ) {
191+
cache[key] = pos;
192+
}
193+
}
194+
return cache.hasOwnProperty(key)? cache[key] : -1;
180195
},
181196

182197
/**
@@ -349,7 +364,7 @@
349364
* @returns {string||null}
350365
* @private
351366
*/
352-
_getKey: function(rec) {
367+
_getKey: function(rec) { //todo rename this to $$getId
353368
return angular.isObject(rec)? rec.$id : null;
354369
},
355370

@@ -366,13 +381,13 @@
366381
$$process: function(event, rec, prevChild) {
367382
var key = this._getKey(rec);
368383
var changed = false;
369-
var pos;
384+
var curPos;
370385
switch(event) {
371386
case 'child_added':
372-
pos = this.$indexFor(key);
387+
curPos = this.$indexFor(key);
373388
break;
374389
case 'child_moved':
375-
pos = this.$indexFor(key);
390+
curPos = this.$indexFor(key);
376391
this._spliceOut(key);
377392
break;
378393
case 'child_removed':
@@ -385,9 +400,9 @@
385400
default:
386401
throw new Error('Invalid event type ' + event);
387402
}
388-
if( angular.isDefined(pos) ) {
403+
if( angular.isDefined(curPos) ) {
389404
// add it to the array
390-
changed = this._addAfter(rec, prevChild) !== pos;
405+
changed = this._addAfter(rec, prevChild) !== curPos;
391406
}
392407
if( changed ) {
393408
// send notifications to anybody monitoring $watch
@@ -433,6 +448,7 @@
433448
if( i === 0 ) { i = this.$list.length; }
434449
}
435450
this.$list.splice(i, 0, rec);
451+
this._indexCache[this._getKey(rec)] = i;
436452
return i;
437453
},
438454

@@ -447,6 +463,7 @@
447463
_spliceOut: function(key) {
448464
var i = this.$indexFor(key);
449465
if( i > -1 ) {
466+
delete this._indexCache[key];
450467
return this.$list.splice(i, 1)[0];
451468
}
452469
return null;
@@ -466,12 +483,13 @@
466483
return list[indexOrItem];
467484
}
468485
else if( angular.isObject(indexOrItem) ) {
469-
var i = list.length;
470-
while(i--) {
471-
if( list[i] === indexOrItem ) {
472-
return indexOrItem;
473-
}
474-
}
486+
// it must be an item in this array; it's not sufficient for it just to have
487+
// a $id or even a $id that is in the array, it must be an actual record
488+
// the fastest way to determine this is to use $getRecord (to avoid iterating all recs)
489+
// and compare the two
490+
var key = this._getKey(indexOrItem);
491+
var rec = this.$getRecord(key);
492+
return rec === indexOrItem? rec : null;
475493
}
476494
return null;
477495
},
@@ -530,4 +548,4 @@
530548
return FirebaseArray;
531549
}
532550
]);
533-
})();
551+
})();

tests/mocks/mock.utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ angular.module('mock.utils', [])
1717
$delegate[method]._super = origMethod;
1818
}
1919

20-
$provide.decorator('$firebaseUtils', function($delegate, $timeout) {
20+
$provide.decorator('$firebaseUtils', function($delegate) {
2121
spyOnCallback($delegate, 'compile');
2222
spyOnCallback($delegate, 'wait');
2323
return $delegate;

tests/unit/firebase.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@ describe('$firebase', function () {
712712
});
713713

714714
it('should batch requests', function() {
715+
$fb.$asObject(); // creates the listeners
715716
flushAll();
716717
$utils.wait.completed.calls.reset();
717718
var ref = $fb.$ref();

0 commit comments

Comments
 (0)