Home >Web Front-end >JS Tutorial >A slightly better 'Map' can get you there more easily...

A slightly better 'Map' can get you there more easily...

Barbara Streisand
Barbara StreisandOriginal
2024-12-09 08:33:12370browse

A slightly better

In JavaScript the Map is a very useful built in class that creates an O(1) lookup between a key and a value.

const myMap = new Map()

for(const file of files) {
    const [,extension] = file.name.split(".")
    if(!myMap.has(extension)) {
        myMap.set(extension, [])
    }
    myMap.get(extension).push(file)
}

You can use Maps for all sorts of things you are likely to do regularly:

  • Creating grouped lists of data, like the above example grouping by file extension

  • Aggregating data, like counting or summing values across a range of keys

// 1) Counting occurrences
const items = ['apple','apple','orange','banana','apple'];
const counts = new Map();
for (const item of items) {
  counts.set(item, (counts.get(item) || 0) + 1);
}
  • Creating rapid lookups to be used in subsequent steps
const users = [
  {id:1,name:'A',role:'admin'},
  {id:2,name:'B',role:'user'},
  {id:3,name:'C',role:'user'}
];
const userMap = new Map();
for (const u of users) {
  userMap.set(u.id, u);
}

Map is preferred to using a simple object ({}) for a couple of reasons, so long as you don't have to store the result using a stringify:

  • It can take keys which are not strings
  • It's slightly faster than an Object even if you are using string keys

There can be a lot of boilerplate and mixed concerns though, if the object you are storing in the map needs construction, which is anything from a simple array to a complex object, this needs to be interspersed with the code that uses it.

const map = new Map()

for(const item of items) {
   if(!map.has(item.type)) {
       const newType = new Type(item.type, getInfoForType(item.type))
       map.set(item.type, newType)
   }
   map.get(item.type).doSomething(item)

}

This "can" be ok, but it becomes harder to keep DRY if you need to update or initialise the value in multiple places.

For this reason I use a MapPlus class, which is an extension to Map that provides a missing key initialiser function that can be supplied to the constructor or as a second parameter to the get if the initialiser does need in context information beyond just the key.

class MapPlus extends Map {
    constructor(missingFunction) {
        super()
        this.missingFunction = missingFunction
    }

    get(key, createIfMissing = this.missingFunction) {
        let result = super.get(key)
        if (!result && createIfMissing) {
            result = createIfMissing(key)
            if (result && result.then) {
                const promise = result.then((value) => {
                    super.set(key, value)
                    return value
                })
                super.set(key, promise)
            } else {
                super.set(key, result)
            }
        }
        return result
    }
}

With this you can just do things like:

const map = new MapPlus(()=>[])

for(const item of items) {
    map.get(item.type).push(item)
}

As if the key is missing it will just make an empty array.

I often need two levels of this so I'll have maps defined like this:

const map = new MapPlus(()=>new MapPlus(()=>[]))
for(const item of items) {
   map.get(item.type).get(item.subType).push(item)
}

The constructor function does get the key being used so we can also do:

const map = new MapPlus((type)=>new Type(type, getInfoForType(type))

for(const item of items) {
    map.get(item.type).doSomething(item)
}

The above is the detailed content of A slightly better 'Map' can get you there more easily.... For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn