17

I have a simple case: a ES6 Map, and I need to add custom get() and set() to it.

But Map is a built-in object, so I'm not sure whether there would be any caveats in doing so. I've tried to search whether it is correct to subclass a Map, and got inconsistent results: it is unclear whether it's allowed by specification, what browser/node.js versions support it, and what side-effects are possible (and what to cover with tests).

As I understand, there are three main approaches to extend Map functionality:

  1. Subclass it. That I've done, and it seems like it works.
class CustomMap extends Map{
    get(key){
        return super.get(key);
    }
    set(key, value){
        return super.set(key, value);
    }
}

Problem with it: a lot of articles on the Internet state that you can run into troubles with extending built-in objects. Most are early 2016, and now is late 2017, testing in Chrome 61. Maybe now it is a safe and supported way of doing it?

  1. Make a wrapper object
const Wrapper = function(){
    this._map = new Map();
    this.get = (key) => {return this._map.get(key);}
    this.set = (key, value) => {this._map.set(key, value);}
    ... everything else
}

The least elegant solution, as I need to implement not just get and set, but all of Map functionality. Also, Wrapper is not an instance of Map.

  1. Use ES6 Proxy
const ProxyMap = function(){
    return new Proxy(new Map(), {
        get(target, key){
            return target.get(key)
        }
        set(target, key, value){
            target.set(key, value);
        }
    }
}

As with extending a class, it is unadvisable to apply Proxy to some built-in types. But again, a lot of time passed since introducing the Proxy specifications; maybe now Map could by proxied in modern browsers?

So, the question is: what way of extending a Map is a correct and robust way in 2017?

3
  • "a lot of articles on the Internet state that you can run into troubles with extending built-in objects" Maybe you are referring to the fact that extending built-in objects isn't well supported in Babel because ES5 doesn't support it. Commented Oct 19, 2017 at 17:09
  • 1
    someone on the internet is as helpful as someone told me yesterday to not eat chocolate , this may not mean that chocolate is not delicious Commented Oct 19, 2017 at 17:13
  • The warnings about extending built-in objects are about adding stuff to Array.prototype, Object.prototype etc for other built-in objects.
    – pawel
    Commented Oct 19, 2017 at 18:29

4 Answers 4

9

It is unclear whether it's allowed by specification

It is. Since ES6, all builtin types are extensible using class syntax

It is unclear what browser/node.js versions support it

They need to support ES6 classes and Map natively. Using a transpiler will usually break it.

1) Subclass it. That I've done, and it seems like it works.

Yes, that is the correct approach.

a lot of articles on the Internet state that you can run into troubles with extending built-in objects. Most are early 2016, and now is late 2017, testing in Chrome 61.

I dunno, the major reference http://perfectionkills.com/extending-native-builtins/ is from 2011. And these articles meant a different thing by "extending builtins": amending their prototype objects with custom objects, e.g. Map.prototype.getWithDefault = function(…) { … };. They do not refer to class … extends ….

Make a wrapper object

This should be fine as well. I don't think you necessarily need your instances to be instanceof Map, if you do you'd have to follow the Liskov substitution principle. Not all "extensions" of a key-value collection would fit that.

3) Use ES6 Proxy - it is unadvisable to apply Proxy to some built-in types.

Indeed, this doesn't work or is at least cumbersome.

4
  • Not all "extensions" of a key-value collection would fit that. What do you mean with "extensions"? And are k/v collections meant as dictionaries (e.g. of type Map {Number: Boolean})?
    – user6445533
    Commented Oct 19, 2017 at 19:05
  • 1
    @ftor Yes, dictionaries/maps/hashes/accociative-arrays/whatever-you-wanna-call-them. I could imagine implementations of customisations that don't follow the LSP. E.g. a multi-map should not be a subclass of map.
    – Bergi
    Commented Oct 19, 2017 at 22:47
  • please show example of how to Commented Nov 6, 2022 at 5:22
  • @AlexanderMills Of how to do what?
    – Bergi
    Commented Nov 6, 2022 at 11:19
4

You can use your first approach using a class which extends Map.

For example, the below is an implementation of queues (FIFO structures) extending Map, which allows you to manage queues in JavaScript with a O(1) time complexity for both insertions and removals:

class MyQueue extends Map {
  constructor() {
    super();
    this.insertionIndex = 0;
    this.removalIndex = 0;
  }

  queue(element) {
    this.set(this.insertionIndex, element);
    this.insertionIndex++;
  }

  dequeue() {
    const el = this.get(this.removalIndex);
    this.delete(this.removalIndex);
    if (el) {
      this.removalIndex++;
    }
    return el;
  }
}

const q = new MyQueue();
q.queue(1);
q.queue(2);
console.log(q.dequeue());
console.log(q.dequeue());
q.queue(3);
console.log(q.dequeue());
console.log(q.dequeue()); // now is empty so dequeue will return undefined with no errors
q.queue(4);
console.log(q.dequeue());

1
  • 1
    Why if (el)? That prevents the queue from storing falsy values
    – Bergi
    Commented Nov 6, 2022 at 11:20
3

The first one is the way to go. class syntax is supported with ES6, as well as Maps and extending Maps is part of this initial defininition too. So every system that supports Maps supports the first method, and the second and the third are just ugly ( concerning performance etc.)

-3

What about overriding the methods?

m = new Map()
m.set('x', 2)
m.get('x')
=> 2
Map.prototype.get = (x) => 'lalala'
m.get('x')
=> lalala
1

Not the answer you're looking for? Browse other questions tagged or ask your own question.