Utilizing WeakMap to effectively store Metadata
Metadata is simply data that holds information about some other piece of data. Seems repetitive but if you were to think of networking and mainly HTTP. If you perform an HTTP request, data is sent or received (HTTP response) together with some HTTP headers. The HTTP headers are considered metadata and specifically descriptive metadata. So as much as you as the recipient of the request might want to read the data sent, you will need the HTTP headers to be able to understand the data you have been sent.
This caught my interest because most of the time, when I am creating a web application. I would want my, let say DOM elements, to hold some information (metadata). I don't want to mutate them by adding attributes so the most obvious way I thought of, is to use a cache object. An example of a cache object would be
const cache = {};
cache["user1"] = {
object: new User("admin"),
meta: {
syncedOn: new Date("2009-11-27T16:45:30"),
}
};
cache["user2"] = {
object: new User("student"),
meta: {
syncedOn: new Date("2009-11-27T09:45:30"),
}
};
Some problems with the example above are:
for (const key in cache) {
// check all users whose last sync were 20 minutes ago
}
// OR
Object.keys(cache).map(someComputationBasedOnMetadata);
Solving iteration in JavaScript
In JavaScript, we can make an object to behave like an Array by adding Symbol.iterator generator function which yields the values required
const cache = {
*[Symbol.iterator]() {
for (const k of Object.keys(this)) {
// you could do more computation
yield this[k];
}
}
};
cache["john_doe"] = {
object: new User("admin"),
meta: {
syncedOn: new Date("2024-05-07T00:45:30")
}
};
cache["jane_doe"] = {
object: new User("root"),
meta: {
syncedOn: new Date("2024-05-07T03:15:30")
}
};
for (const key of Object.keys(cache)) {
const metadata = cache[key];
// some computation done here!
}
Managing garbage collection
Here is where a tradeoff comes in. We are going to introduce a JavaScript data structure WeakMap.
A WeakMap is a collection of key/value pairs where the key is an object or non-registered symbol and the values are normal (or arbitrary) JavaScript types e.g. Boolean or Number or JavaScript objects ({ }).
Although it has a similar API to a Map it has one main advantage which is the keys provided are never restricted from being garbage collected. This means:
const wm = new WeakMap();
const signupForm = document.querySelector("form[data-purpose='signup']");
wm.set(
signupForm,
{
lastFilledOn: new Date("2024-05-09T13:05:00")
}
);
领英推荐
In an event where signupForm is no longer referenced, it will be garbage collected. This means later on wm.has(signupForm) is returns false.
Now the tradeoff is we cannot iterate over a WeakMap. The reason is because unlike Maps, WeakMaps do not keep an array or list of keys since its keys are non-deterministic or garbage collectable.
WeakMap use case
Going back to our cache example, we can expand it by assuming a real-world use case.
Let's say we have a table of company employees. We are tasked to improve the table's functionality by enabling in-memory editing. We can cache the state of our form per row using WeakMap following the pattern.
type EditFormCache = WeakMap<HTMLFormElement, "editing" | "stalled">;
This way when we toggle back and forth between a normal table row and a form row, we can only get forms which have not yet been garbage collected stored in our cache.
const wm = new WeakMap();
const form = document.querySelectorAll("form.table-row-edit-form");
for (const f of form) {
wm.set(f, "stalled");
}
Then when the user is typing in an input you can capture the parent (HTMLFormElement) and update the state
const form = document.querySelector("input.my-input").parentElement;
if (wm.has(form)) {
const state = wm.get(form);
switch (state) {
case "editing": {
wm.set(form, "stalled");
break;
}
case "stalled": {
wm.set(form, "editing");
break;
}
}
}
Then maybe after updating the state we can use WebSocket API to save the data entered if state is "stalled" or even inform other clients of the current state of the table row i.e. "editing".
Closing remarks
The example above may resemble a real-world scenario but the truth of the matter is there is more work to it than meets the eye. That being said, you will notice that,
All in all, I found it to be an interesting JavaScript collection. I am planning to see how I can create an Inversion of Control (IoC) library with it. I would love to hear the cool ways you have or would want to use WeakMap.
Thank you and Happy coding!
Software Developer, Student.
6 个月Nice job!!