DEV Community

Pranev
Pranev

Posted on • Updated on

Extending the JavaScript Map

This is meant to provide an understanding of how stuff work. This is not the "best" method to do any of the mentioned stuff.

A map is an abstract data type composed of key-value pairs. JavaScript comes with the built-in Map object that allows creating a map easily.

 const myMap = new Map<string, string>();
 map.set("foo", "bar");
 console.log(map.get("foo"));
Enter fullscreen mode Exit fullscreen mode

Pretty easy, isn't it?

However, the methods available on a Map are very limited, namely:

  • Map.prototype.clear()
  • Map.prototype.delete()
  • Map.prototype.entries()
  • Map.prototype.forEach()
  • Map.prototype.get()
  • Map.prototype.has()
  • Map.prototype.keys()
  • Map.prototype.set()
  • Map.prototype.values()

In this article, you'll be guided to extend the Map object and add more methods for ease of use. The following are the list of methods we will be implementing:

  • find()
  • filter()
  • map()
  • reduce()

Now let's move on to implementing them.

Extending a Class

Classes in JavaScript can be extended using the extends keyword.

 class ExtendedMap extends Map {
    constructor() {}
 }
Enter fullscreen mode Exit fullscreen mode

Since we are extending an existing class, we should follow up with a super() call.

 class ExtendedMap extends Map {
    constructor() {
        super();
    }
 }
Enter fullscreen mode Exit fullscreen mode

Cool. Next comes adding custom methods.

Implementing Methods

 class ExtendedMap extends Map {
    constructor() {
        super();
    }
    foo() {
        console.log("bar")
    }
 }
Enter fullscreen mode Exit fullscreen mode

You should now be able to access the extended map.

const myMap = new ExtendedMap();
myMap.foo();
Enter fullscreen mode Exit fullscreen mode
bar
Enter fullscreen mode Exit fullscreen mode

Map.find()

The find() method performs a linear search on the collection and returns the first element that passes our condition.

For this, we first convert our Map into an iterator. To do so, we use Map.prototype.entries(). We can then iterate over the result using a for..of loop.

 find() {
    for (const [k, v] of this.entries()) {

    }
 }
Enter fullscreen mode Exit fullscreen mode

Our find() method needs one parameter, the test function. The function should return a truthy value in order to return an element.

 find(fn) {
    for (const [k, v] of this.entries()) {

    }
 }
Enter fullscreen mode Exit fullscreen mode

Next is of course, running our test function on each entry in the map.

 find(fn) {
    for (const [k, v] of this.entries()) {
        if(fn(v, k)) return v;
    }
 }
Enter fullscreen mode Exit fullscreen mode

There we have, our simple find() method. However, this method will return NOTHING if it didn't find a match. In that case, we can return a null
or undefined.

 find(fn) {
    for (const [k, v] of this.entries()) {
        if(fn(v, k)) return v;
    }
    return undefined;
 }
Enter fullscreen mode Exit fullscreen mode

Map.filter()

The filter() method performs a linear search on the collection and returns all elements that pass our condition.

The working is the same as our find() method.

 filter(fn) {
    for (const [k, v] of this.entries()) {

    }
 }
Enter fullscreen mode Exit fullscreen mode

The only change is that we are returning an array of elements now.

 filter(fn) {
    const res = [];
    for (const [k, v] of this.entries()) {
        if(fn(v, k)) res.push(v);
    }
    return res;
 }
Enter fullscreen mode Exit fullscreen mode

Unlike find(), method will return an empty array even if it didn't find a match. We don't need to explicitly return a fallback.

Map.map()

map() is a bit different. It is a method which runs a function on every element in the collection and returns an array of results.

Starting from where we left our filter,

 map(fn) {
    const res = [];
    for (const [k, v] of this.entries()) {
        if(fn(v, k)) res.push(v);
    }
    return res;
 }
Enter fullscreen mode Exit fullscreen mode

All we need to do is, remove the condition and push the function result instead.

 map(fn) {
    const res = [];
    for (const [k, v] of this.entries()) {
        res.push(fn(v, k));
    }
    return res;
 }
Enter fullscreen mode Exit fullscreen mode

Easy, is it not? Now comes the final part of this guide.

Map.reduce()

The reduce() method accepts a reducer callback as a parameter and executes the callback on each element, with the previous iteration's result as a parameter. Like a chain.

Let's continue from where we left our map().

 reduce(fn) {
    const res = [];
    for (const [k, v] of this.entries()) {
        res.push(fn(v, k));
    }
    return res;
 }
Enter fullscreen mode Exit fullscreen mode

We will need two parameters, one being the callback and the other being the initial value of our result.

 reduce(fn, first) {
    let res = first;
    for (const [k, v] of this.entries()) {
        res = fn(res, [k, v]);
    }
    return res;
 }
Enter fullscreen mode Exit fullscreen mode

And that's it! We have implemented reduce() for our extended Map.

Our result

Check out the full source code with even more methods at retraigo/bettermap.

 class ExtendedMap extends Map {
    constructor() {
        super();
    }
    find(fn) {
        for (const [k, v] of this.entries()) {
            if(fn(v, k)) return v;
        }
        return undefined;
    }
    filter(fn) {
        const res = [];
        for (const [k, v] of this.entries()) {
            if(fn(v, k)) res.push(v);
        }
        return res;
    }
    map(fn) {
        const res = [];
        for (const [k, v] of this.entries()) {
            res.push(fn(v, k));
        }
        return res;
    }
    reduce(fn, first) {
        const res = first;
        for (const [k, v] of this.entries()) {
            res = fn(res, [k, v]);
        }
        return res;
    }
 }
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
efpage profile image
Eckehard

Very nice extension. Thank you for sharing.

Did you do some performance tests?

Collapse
 
retraigo profile image
Pranev

Kinda. You won't notice much difference till the map has millions of items. If it is going to scale that big, you might wanna use a bunch of while loops over the current for..of loops or look for a better option than a Map.