@@ -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 */
661710export 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