简体   繁体   中英

How to properly extend ES6 Map

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?

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, eg 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.

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());

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.)

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM