Basically if you want to track any information related to an object that should for some reason not be present on the object itself, you could use a map.
A simple example could be to track how many times an operation was done relating to an object.
Here's a demonstration where we keep track of how much food each animal (instance) has eaten without affecting the animal itself:
function Animal(type) {
this.type = type;
}
// now let's say somewhere else in our program
// we want to have a farm where animals can eat.
// We want the farm to keep track of how much each animal ate but
// the animal itself doesn't need to know how much food it has eaten.
const AnimalFarm = (() => {
const mapOfAnimalToAmountOfFood = new Map();
return {
feedAnimal(animal, amountOfFood) {
// if the animal is being fed the first time
// initialize the amount to 0
if (!mapOfAnimalToAmountOfFood.has(animal)) {
mapOfAnimalToAmountOfFood.set(animal, 0)
}
// add amountOfFood to the amount of food already eaten
mapOfAnimalToAmountOfFood.set(
animal,
mapOfAnimalToAmountOfFood.get(animal) + amountOfFood
)
},
getAmountEaten: function(animal) {
return mapOfAnimalToAmountOfFood.get(animal)
}
}
})()
const dog1 = new Animal('dog')
const dog2 = new Animal('dog')
AnimalFarm.feedAnimal(dog1, 300)
AnimalFarm.feedAnimal(dog1, 500)
AnimalFarm.feedAnimal(dog2, 1234)
console.log(
`dog1 ate ${AnimalFarm.getAmountEaten(dog1)} total`
)
console.log(
`dog2 ate ${AnimalFarm.getAmountEaten(dog2)} total`
)
In general, a main reason for creating a map of objects to some data is that you can maintain local information about an object which, although directly related to this object, is fully contained in your own module and doesn't pollute any other parts of the system (Separation of Concerns).
Another example could be a graph which has a map of objects representing the nodes to a list of other nodes they have a connection to (useful for example in Dijkstra's algorithm):
Map<Place, ListOfPlacesICanGoTo>
This allows you to have a more pure Place
object by separating this relationship rather than putting a direct Place.listOfPlaces
link within the object itself. This is particularly useful if a Place
is used in other contexts where listOfPlaces
is not needed or even doesn't make sense.
Another common usage of objects as keys in a map-like structure is when using a WeakMap
, because it's also more memory efficient by allowing each key object to be garbage collected as soon as nothing else references it.
An example could be the underlying implementation for process.on('unhandledRejection')
in node which uses a WeakMap
to keep track of promises that were rejected but no error handlers dealt with the rejection within the current tick.
As far as using a function as a key, I would personally think this is less useful but certainly not useless.
One useful example could be to check if a certain function was already passed in before and not invoke it again but return a cached result. This could prevent repetitive execution of potentially expensive operations.
const map = new Map();
function invokeOrGetFromCache(fn) {
if (map.has(fn)) {
return map.get(fn);
}
const result = fn();
map.set(fn, result);
return result;
}
function exampleFn() {
console.log('start');
for (i = 0; i < 100000; i++);
console.log('done');
return true;
}
console.log(
invokeOrGetFromCache(exampleFn) // runs exampleFn
);
console.log(
invokeOrGetFromCache(exampleFn) // retrieves from cache
);
As with objects, using a WeakMap
could be preferable in these situations as well, for efficiency reasons.