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"));
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() {}
}
Since we are extending an existing class, we should follow up with a super()
call.
class ExtendedMap extends Map {
constructor() {
super();
}
}
Cool. Next comes adding custom methods.
Implementing Methods
class ExtendedMap extends Map {
constructor() {
super();
}
foo() {
console.log("bar")
}
}
You should now be able to access the extended map.
const myMap = new ExtendedMap();
myMap.foo();
bar
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()) {
}
}
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()) {
}
}
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;
}
}
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;
}
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()) {
}
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
}
Top comments (2)
Very nice extension. Thank you for sharing.
Did you do some performance tests?
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 currentfor..of
loops or look for a better option than a Map.