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

Commit f8ca2ec

Browse files
author
Jacob Wenger
committed
Merge pull request #461 from firebase/kato-indexfor
Optimize $indexFor by caching index locations in a weak map
2 parents 046d872 + 3cb6742 commit f8ca2ec

File tree

3 files changed

+33
-14
lines changed

3 files changed

+33
-14
lines changed

src/FirebaseArray.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@
6464
this._promise = readyPromise;
6565
this._destroyFn = destroyFn;
6666

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

183198
/**
@@ -367,13 +382,13 @@
367382
$$process: function(event, rec, prevChild) {
368383
var key = this.$$getKey(rec);
369384
var changed = false;
370-
var pos;
385+
var curPos;
371386
switch(event) {
372387
case 'child_added':
373-
pos = this.$indexFor(key);
388+
curPos = this.$indexFor(key);
374389
break;
375390
case 'child_moved':
376-
pos = this.$indexFor(key);
391+
curPos = this.$indexFor(key);
377392
this._spliceOut(key);
378393
break;
379394
case 'child_removed':
@@ -386,9 +401,9 @@
386401
default:
387402
throw new Error('Invalid event type: ' + event);
388403
}
389-
if( angular.isDefined(pos) ) {
404+
if( angular.isDefined(curPos) ) {
390405
// add it to the array
391-
changed = this._addAfter(rec, prevChild) !== pos;
406+
changed = this._addAfter(rec, prevChild) !== curPos;
392407
}
393408
if( changed ) {
394409
// send notifications to anybody monitoring $watch
@@ -434,6 +449,7 @@
434449
if( i === 0 ) { i = this.$list.length; }
435450
}
436451
this.$list.splice(i, 0, rec);
452+
this._indexCache[this.$$getKey(rec)] = i;
437453
return i;
438454
},
439455

@@ -448,6 +464,7 @@
448464
_spliceOut: function(key) {
449465
var i = this.$indexFor(key);
450466
if( i > -1 ) {
467+
delete this._indexCache[key];
451468
return this.$list.splice(i, 1)[0];
452469
}
453470
return null;
@@ -467,12 +484,13 @@
467484
return list[indexOrItem];
468485
}
469486
else if( angular.isObject(indexOrItem) ) {
470-
var i = list.length;
471-
while(i--) {
472-
if( list[i] === indexOrItem ) {
473-
return indexOrItem;
474-
}
475-
}
487+
// it must be an item in this array; it's not sufficient for it just to have
488+
// a $id or even a $id that is in the array, it must be an actual record
489+
// the fastest way to determine this is to use $getRecord (to avoid iterating all recs)
490+
// and compare the two
491+
var key = this.$$getKey(indexOrItem);
492+
var rec = this.$getRecord(key);
493+
return rec === indexOrItem? rec : null;
476494
}
477495
return null;
478496
},

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)