Indexers in JavaScript
Indexers are properties that allow you to index instances of a class like arrays by using the [] notation. In this article, learn how to use them in JavaScript.
Join the DZone community and get the full member experience.
Join For FreeSome modern object-oriented languages have the concept of indexers, which are properties that allow you to index instances of a class just like arrays by using the [] notation. In this article, I would like to show you how to do it in modern JavaScript.
Here’s an example in C#:
class Statuses {
private Dictionary<string, Status> inner = new Dictionary<string, Status>();
// implement the indexer
public Status this[string name] {
get => inner[name];
set => inner[name] = value;
}
}
class Program {
static void Main(string[] args) {
Statuses s = new Statuses();
// refer to the property by using the [] notation
s["paid"] = Status.Paid;
s["pending"] = Status.Pending;
}
}
Unfortunately, this is not yet possible in modern ES6. However, ES6 has certain mechanisms that allow you to achieve this kind of behavior, although this may look less elegant than in C#.
ES6 has a special proxy class that allows you to intercept calls to the base class. Thus, it becomes possible to handle calls to class fields in a special way.
Let’s repeat our C# example in JS without any tricks.
class Statuses {
#statuses = new Map();
getStatus(key) {
// there may be complex logic here
return this.#statuses.get(key);
}
setStatus(key, value) {
//there may be complex logic here
this.#statuses.set(key, value);
}
hasStatus(key) {
return this.#statuses.has(key);
}
}
const statuses = new Statuses();
statuses.setStatus('paid', ...);
statuses.setStatus('pending', ...);
statuses.hasStatus('paid'); => true
To add a status to the set, we use setStatus() instead of the [] notation. Let’s fix this by adding some proxy magic. To do this, let’s declare a constructor in the Statuses class and return its proxy wrapper with get and set accessors instead of the Statuses instance.
constructor() {
return new Proxy(this, {
// overrides getting Statuses.name
get(target, name) {
if (name in target) {
const result = target[name];
return typeof result === 'function' ? result.bind(target) : result;
}
return target.getStatus(name);
},
// overrides getting Statuses.name
set(target, name, value) {
if (name in target)
target[name] = value;
else
target.setStatus(name, value);
return true;
}
});
}
Here we need to clarify some code for the get accessor:
if (name in target) {…}
This fragment is needed to access the class properties and methods (for example, hasStatus), and only if the class has no such property or method, the call will go to getStatus().
return typeof result === 'function' ? result.bind(target) : result;
Here, functions are bound to target (i.e. to the Status instance). Otherwise, they will receive this = Proxy inside.
After creating such a constructor, the required syntax becomes available to us:
const statuses = new Statuses();
statuses['paid'] = …;
statuses['pending'] = …;
statuses.has('paid'); // true
console.log(statuses['pending']);
Although we have achieved the desired result, the JS code looks more like a crutch and I would recommend using it only if you port the existing code from another language (C#, Delphi) where such properties were actively used.
In turn, I’d be interested to know if there are any other ways to implement the [] notation for invoking a getter/setter without using a proxy.
Opinions expressed by DZone contributors are their own.
Comments