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

Commit 641e13a

Browse files
committed
refactor(*): replace HashMap with NgMap
For the time being, we will be using `NgMap`, which is an API-compatible implementation of native `Map` (for the features required in Angular). This will make it easy to switch to using the native implementations, once they become more stable. Note: At the moment some native implementations are still buggy (often in subtle ways) and can cause hard-to-debug failures.) Closes #15483
1 parent 028fa1a commit 641e13a

16 files changed

+141
-160
lines changed

src/.eslintrc.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150

151151
/* apis.js */
152152
"hashKey": false,
153-
"HashMap": false,
153+
"NgMap": false,
154154

155155
/* urlUtils.js */
156156
"urlResolve": false,

src/AngularPublic.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@
7070
$$ForceReflowProvider,
7171
$InterpolateProvider,
7272
$IntervalProvider,
73-
$$HashMapProvider,
7473
$HttpProvider,
7574
$HttpParamSerializerProvider,
7675
$HttpParamSerializerJQLikeProvider,
@@ -79,6 +78,7 @@
7978
$jsonpCallbacksProvider,
8079
$LocationProvider,
8180
$LogProvider,
81+
$$MapProvider,
8282
$ParseProvider,
8383
$RootScopeProvider,
8484
$QProvider,
@@ -260,7 +260,7 @@ function publishExternalAPI(angular) {
260260
$window: $WindowProvider,
261261
$$rAF: $$RAFProvider,
262262
$$jqLite: $$jqLiteProvider,
263-
$$HashMap: $$HashMapProvider,
263+
$$Map: $$MapProvider,
264264
$$cookieReader: $$CookieReaderProvider
265265
});
266266
}

src/apis.js

+55-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
43
/**
54
* Computes a hash of an 'obj'.
65
* Hash of a:
@@ -33,49 +32,69 @@ function hashKey(obj, nextUidFn) {
3332
return key;
3433
}
3534

36-
/**
37-
* HashMap which can use objects as keys
38-
*/
39-
function HashMap(array, isolatedUid) {
40-
if (isolatedUid) {
41-
var uid = 0;
42-
this.nextUid = function() {
43-
return ++uid;
44-
};
45-
}
46-
forEach(array, this.put, this);
35+
// A minimal ES2015 Map implementation.
36+
// Should be bug/feature equivalent to the native implementations of supported browsers
37+
// (for the features required in Angular).
38+
// See https://kangax.github.io/compat-table/es6/#test-Map
39+
var nanKey = Object.create(null);
40+
function NgMapShim() {
41+
this._keys = [];
42+
this._values = [];
43+
this._lastKey = NaN;
44+
this._lastIndex = -1;
4745
}
48-
HashMap.prototype = {
49-
/**
50-
* Store key value pair
51-
* @param key key to store can be any type
52-
* @param value value to store can be any type
53-
*/
54-
put: function(key, value) {
55-
this[hashKey(key, this.nextUid)] = value;
46+
NgMapShim.prototype = {
47+
_idx: function(key) {
48+
if (key === this._lastKey) {
49+
return this._lastIndex;
50+
}
51+
this._lastKey = key;
52+
this._lastIndex = this._keys.indexOf(key);
53+
return this._lastIndex;
54+
},
55+
_transformKey: function(key) {
56+
return isNumberNaN(key) ? nanKey : key;
5657
},
57-
58-
/**
59-
* @param key
60-
* @returns {Object} the value for the key
61-
*/
6258
get: function(key) {
63-
return this[hashKey(key, this.nextUid)];
59+
key = this._transformKey(key);
60+
var idx = this._idx(key);
61+
if (idx !== -1) {
62+
return this._values[idx];
63+
}
6464
},
65+
set: function(key, value) {
66+
key = this._transformKey(key);
67+
var idx = this._idx(key);
68+
if (idx === -1) {
69+
idx = this._lastIndex = this._keys.length;
70+
}
71+
this._keys[idx] = key;
72+
this._values[idx] = value;
6573

66-
/**
67-
* Remove the key/value pair
68-
* @param key
69-
*/
70-
remove: function(key) {
71-
var value = this[key = hashKey(key, this.nextUid)];
72-
delete this[key];
73-
return value;
74+
// Support: IE11
75+
// Do not `return this` to simulate the partial IE11 implementation
76+
},
77+
delete: function(key) {
78+
key = this._transformKey(key);
79+
var idx = this._idx(key);
80+
if (idx === -1) {
81+
return false;
82+
}
83+
this._keys.splice(idx, 1);
84+
this._values.splice(idx, 1);
85+
this._lastKey = NaN;
86+
this._lastIndex = -1;
87+
return true;
7488
}
7589
};
7690

77-
var $$HashMapProvider = [/** @this */function() {
91+
// For now, always use `NgMapShim`, even if `window.Map` is available. Some native implementations
92+
// are still buggy (often in subtle ways) and can cause hard-to-debug failures. When native `Map`
93+
// implementations get more stable, we can reconsider switching to `window.Map` (when available).
94+
var NgMap = NgMapShim;
95+
96+
var $$MapProvider = [/** @this */function() {
7897
this.$get = [function() {
79-
return HashMap;
98+
return NgMap;
8099
}];
81100
}];

src/auto/injector.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ function createInjector(modulesToLoad, strictDi) {
649649
var INSTANTIATING = {},
650650
providerSuffix = 'Provider',
651651
path = [],
652-
loadedModules = new HashMap([], true),
652+
loadedModules = new NgMap(),
653653
providerCache = {
654654
$provide: {
655655
provider: supportObject(provider),
@@ -757,7 +757,7 @@ function createInjector(modulesToLoad, strictDi) {
757757
var runBlocks = [], moduleFn;
758758
forEach(modulesToLoad, function(module) {
759759
if (loadedModules.get(module)) return;
760-
loadedModules.put(module, true);
760+
loadedModules.set(module, true);
761761

762762
function runInvokeQueue(queue) {
763763
var i, ii;

src/ng/animate.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ var $$CoreAnimateJsProvider = /** @this */ function() {
6060
// this is prefixed with Core since it conflicts with
6161
// the animateQueueProvider defined in ngAnimate/animateQueue.js
6262
var $$CoreAnimateQueueProvider = /** @this */ function() {
63-
var postDigestQueue = new HashMap();
63+
var postDigestQueue = new NgMap();
6464
var postDigestElements = [];
6565

6666
this.$get = ['$$AnimateRunner', '$rootScope',
@@ -139,7 +139,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() {
139139
jqLiteRemoveClass(elm, toRemove);
140140
}
141141
});
142-
postDigestQueue.remove(element);
142+
postDigestQueue.delete(element);
143143
}
144144
});
145145
postDigestElements.length = 0;
@@ -154,7 +154,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() {
154154

155155
if (classesAdded || classesRemoved) {
156156

157-
postDigestQueue.put(element, data);
157+
postDigestQueue.set(element, data);
158158
postDigestElements.push(element);
159159

160160
if (postDigestElements.length === 1) {

src/ng/directive/select.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var SelectController =
1616
['$element', '$scope', /** @this */ function($element, $scope) {
1717

1818
var self = this,
19-
optionsMap = new HashMap();
19+
optionsMap = new NgMap();
2020

2121
self.selectValueMap = {}; // Keys are the hashed values, values the original values
2222

@@ -137,7 +137,7 @@ var SelectController =
137137
self.emptyOption = element;
138138
}
139139
var count = optionsMap.get(value) || 0;
140-
optionsMap.put(value, count + 1);
140+
optionsMap.set(value, count + 1);
141141
// Only render at the end of a digest. This improves render performance when many options
142142
// are added during a digest and ensures all relevant options are correctly marked as selected
143143
scheduleRender();
@@ -148,13 +148,13 @@ var SelectController =
148148
var count = optionsMap.get(value);
149149
if (count) {
150150
if (count === 1) {
151-
optionsMap.remove(value);
151+
optionsMap.delete(value);
152152
if (value === '') {
153153
self.hasEmptyOption = false;
154154
self.emptyOption = undefined;
155155
}
156156
} else {
157-
optionsMap.put(value, count - 1);
157+
optionsMap.set(value, count - 1);
158158
}
159159
}
160160
};
@@ -606,9 +606,9 @@ var selectDirective = function() {
606606

607607
// Write value now needs to set the selected property of each matching option
608608
selectCtrl.writeValue = function writeMultipleValue(value) {
609-
var items = new HashMap(value);
610609
forEach(element.find('option'), function(option) {
611-
option.selected = isDefined(items.get(option.value)) || isDefined(items.get(selectCtrl.selectValueMap[option.value]));
610+
option.selected = !!value && (includes(value, option.value) ||
611+
includes(value, selectCtrl.selectValueMap[option.value]));
612612
});
613613
};
614614

src/ngAnimate/animateQueue.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,15 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
100100
return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
101101
});
102102

103-
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
103+
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$Map',
104104
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
105105
'$$isDocumentHidden',
106-
function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
106+
function($$rAF, $rootScope, $rootElement, $document, $$Map,
107107
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow,
108108
$$isDocumentHidden) {
109109

110-
var activeAnimationsLookup = new $$HashMap();
111-
var disabledElementsLookup = new $$HashMap();
110+
var activeAnimationsLookup = new $$Map();
111+
var disabledElementsLookup = new $$Map();
112112
var animationsEnabled = null;
113113

114114
function postDigestTaskFactory() {
@@ -291,7 +291,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
291291
bool = !disabledElementsLookup.get(node);
292292
} else {
293293
// (element, bool) - Element setter
294-
disabledElementsLookup.put(node, !bool);
294+
disabledElementsLookup.set(node, !bool);
295295
}
296296
}
297297
}
@@ -599,7 +599,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
599599
animationDetails.runner.end();
600600
/* falls through */
601601
case PRE_DIGEST_STATE:
602-
activeAnimationsLookup.remove(child);
602+
activeAnimationsLookup.delete(child);
603603
break;
604604
}
605605
}
@@ -608,7 +608,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
608608

609609
function clearElementAnimationState(node) {
610610
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
611-
activeAnimationsLookup.remove(node);
611+
activeAnimationsLookup.delete(node);
612612
}
613613

614614
/**
@@ -713,7 +713,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
713713
var newValue = oldValue
714714
? extend(oldValue, details)
715715
: details;
716-
activeAnimationsLookup.put(node, newValue);
716+
activeAnimationsLookup.set(node, newValue);
717717
}
718718
}];
719719
}];

src/ngAnimate/animation.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
2121
return element.data(RUNNER_STORAGE_KEY);
2222
}
2323

24-
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
25-
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
24+
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$Map', '$$rAFScheduler',
25+
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$Map, $$rAFScheduler) {
2626

2727
var animationQueue = [];
2828
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2929

3030
function sortAnimations(animations) {
3131
var tree = { children: [] };
32-
var i, lookup = new $$HashMap();
32+
var i, lookup = new $$Map();
3333

34-
// this is done first beforehand so that the hashmap
34+
// this is done first beforehand so that the map
3535
// is filled with a list of the elements that will be animated
3636
for (i = 0; i < animations.length; i++) {
3737
var animation = animations[i];
38-
lookup.put(animation.domNode, animations[i] = {
38+
lookup.set(animation.domNode, animations[i] = {
3939
domNode: animation.domNode,
4040
fn: animation.fn,
4141
children: []
@@ -54,7 +54,7 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
5454

5555
var elementNode = entry.domNode;
5656
var parentNode = elementNode.parentNode;
57-
lookup.put(elementNode, entry);
57+
lookup.set(elementNode, entry);
5858

5959
var parentEntry;
6060
while (parentNode) {

src/ngMock/angular-mocks.js

-6
Original file line numberDiff line numberDiff line change
@@ -2985,12 +2985,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
29852985
delete fn.$inject;
29862986
});
29872987

2988-
angular.forEach(currentSpec.$modules, function(module) {
2989-
if (module && module.$$hashKey) {
2990-
module.$$hashKey = undefined;
2991-
}
2992-
});
2993-
29942988
currentSpec.$injector = null;
29952989
currentSpec.$modules = null;
29962990
currentSpec.$providerInjector = null;

test/.eslintrc.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@
143143

144144
/* apis.js */
145145
"hashKey": false,
146-
"HashMap": false,
146+
"NgMapShim": false,
147147

148148
/* urlUtils.js */
149149
"urlResolve": false,

0 commit comments

Comments
 (0)