Skip to content

Commit 20821c1

Browse files
authored
Merge pull request #966 from JonasBa/jb/core/hashmap-es6
feat(polyfill): use native map if available
2 parents d3a54ed + e9a13bc commit 20821c1

File tree

1 file changed

+67
-14
lines changed

1 file changed

+67
-14
lines changed

src/core/util.ts

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -653,20 +653,68 @@ export function isPrimitive(obj: any): boolean {
653653
return obj[primitiveKey];
654654
}
655655

656+
interface MapInterface<T, KEY extends string | number = string | number> {
657+
delete(key: KEY): boolean;
658+
get(key: KEY): T | undefined
659+
set(key: KEY, value: T): this
660+
keys(): KEY[];
661+
forEach(callback: (value: T, key: KEY) => void): void
662+
}
663+
664+
class MapPolyfill<T, KEY extends string | number = string | number> implements MapInterface<T, KEY> {
665+
private data: Record<KEY, T> = {} as Record<KEY, T>;
666+
667+
delete(key: KEY): boolean {
668+
const existed = this.data.hasOwnProperty(key);
669+
if (existed) {
670+
delete this.data[key];
671+
}
672+
return existed;
673+
}
674+
get(key: KEY): T | undefined {
675+
return this.data[key];
676+
}
677+
set(key: KEY, value: T): this {
678+
this.data[key] = value;
679+
return this;
680+
}
681+
keys(): KEY[] {
682+
return keys(this.data);
683+
}
684+
forEach(callback: (value: T, key: KEY) => void): void {
685+
// This is a potential performance bottleneck, see details in
686+
// https://github.com/ecomfe/zrender/issues/965, however it is now
687+
// less likely to occur as we default to native maps when possible.
688+
const data = this.data;
689+
for (const key in data) {
690+
if (data.hasOwnProperty(key)) {
691+
callback(data[key], key);
692+
}
693+
}
694+
}
695+
}
696+
697+
// We want to use native Map if it is available, but we do not want to polyfill the global scope
698+
// in case users ship their own polyfills or patch the native map object in any way.
699+
const isNativeMapSupported = typeof Map === 'function';
700+
function maybeNativeMap<T, KEY extends string | number = string | number>(): MapInterface<T, KEY> {
701+
// Map may be a native class if we are running in an ES6 compatible environment.
702+
// eslint-disable-next-line
703+
return (isNativeMapSupported ? new Map<KEY, T>() : new MapPolyfill<T, KEY>()) as MapInterface<T, KEY>;
704+
}
656705

657706
/**
658707
* @constructor
659-
* @param {Object} obj Only apply `ownProperty`.
708+
* @param {Object} obj
660709
*/
661710
export class HashMap<T, KEY extends string | number = string | number> {
662-
663-
data: {[key in KEY]: T} = {} as {[key in KEY]: T};
711+
data: MapInterface<T, KEY>
664712

665713
constructor(obj?: HashMap<T, KEY> | { [key in KEY]?: T } | KEY[]) {
666714
const isArr = isArray(obj);
667715
// Key should not be set on this, otherwise
668716
// methods get/set/... may be overrided.
669-
this.data = {} as {[key in KEY]: T};
717+
this.data = maybeNativeMap<T, KEY>();
670718
const thisMap = this;
671719

672720
(obj instanceof HashMap)
@@ -682,31 +730,36 @@ export class HashMap<T, KEY extends string | number = string | number> {
682730
// (We usually treat `null` and `undefined` as the same, different
683731
// from ES6 Map).
684732
get(key: KEY): T {
685-
return this.data.hasOwnProperty(key) ? this.data[key] : null;
733+
return this.data.get(key);
686734
}
687735
set(key: KEY, value: T): T {
688736
// Comparing with invocation chaining, `return value` is more commonly
689737
// used in this case: `const someVal = map.set('a', genVal());`
690-
return (this.data[key] = value);
738+
this.data.set(key, value);
739+
return value;
691740
}
692741
// Although util.each can be performed on this hashMap directly, user
693742
// should not use the exposed keys, who are prefixed.
694743
each<Context>(
695744
cb: (this: Context, value?: T, key?: KEY) => void,
696745
context?: Context
697746
) {
698-
for (let key in this.data) {
699-
if (this.data.hasOwnProperty(key)) {
700-
cb.call(context, this.data[key], key);
701-
}
702-
}
747+
this.data.forEach((value, key) => {
748+
cb.call(context, value, key);
749+
});
703750
}
704751
keys(): KEY[] {
705-
return keys(this.data);
752+
// Native map return an iterator so we need to convert it to an array
753+
if (isNativeMapSupported) {
754+
return Array.from(this.data.keys());
755+
}
756+
757+
// Polyfilled map return and Array<KEYS> for keys
758+
return this.data.keys();
706759
}
707760
// Do not use this method if performance sensitive.
708-
removeKey(key: KEY) {
709-
delete this.data[key];
761+
removeKey(key: KEY): void {
762+
this.data.delete(key);
710763
}
711764
}
712765

0 commit comments

Comments
 (0)