The topic discussed here originates from a previous discussion on a method decorator in Typescript. In some scenarios, there are `get` methods in a Typescript class that involve intensive computations. Some of these methods always return the same result and do not change based on the instance's state. However, they are called multiple times on the same instance in the code. It would be efficient to execute the calculations only once, rather than every time the method is accessed. Here's an example:
class Cell {
angle: number;
count: number;
private _cosine?: number;
constructor(angle) {
this.angle = angle;
this.count = 0;
}
get cosine() {
if (this.count) return this._cosine;
this._cosine = Math.cos(this.angle);
this.count++;
return this._cosine;
}
}
const cells = Array.from({ length: 100 }).map(
(_, i) => new Cell(i * 180 * Math.PI)
);
cells.forEach((cell) => {
for (i = 0; i < 100; i++) {
const cosine = cell.cosine;
}
});
When `cell.cosine` is first accessed, it runs the heavy computation and stores the result in the private property `_cosine`. Subsequent calls simply return this value. The `.count` for any `cell` remains at 1, even though `cell.cosine` is accessed 100 times per instance.
Implementing with a Decorator
The current approach requires creating a separate private property and including logic for each individual property needing the once-only behavior. There have been discussions about using decorators to achieve this optimization. While solutions exist for method decorators, adapting them for getters/accessors hasn't been straightforward as getters need to return a value every time they're called.
Is there a way to create a decorator specifically for getter methods that executes the computation only on the first call and returns the calculated value thereafter?
Addressing Potential Questions
You might wonder why not assign the value in the constructor instead? While ideal for performance improvement, certain scenarios like the one below may make this approach impractical:
class Cell {
position: number;
count: number;
constructor(position) {
this.position = position;
this.count = 0;
}
get neighbors() {
let neighbors = [];
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
neighbors.push(
new Cell([x + i, y + j]),
);
}
}
return neighbors;
}
}
In cases like this, where calling `cell.neighbor` creates multiple new instances, assigning `this.neighbors` in the constructor triggers an infinite loop. Although manually implementing a check for already computed values is a solution, utilizing a decorator appears more elegant. Performance tests indicate a slight enhancement by incorporating this decorator (though not as significant as initializing in the constructor).