缓存 getter 的最佳方法是什么(没有装饰器)?



;高速缓存";getter是一种只调用一次值并重用该值而不重新计算的方法。我正在寻找";最好的";缓存getter的方法,最好是用最短的语法和键入。有很多库只允许您在getter上使用像@cache这样的装饰器,它的行为方式与我所描述的一样,我不想使用装饰器。

打字游戏场

有一种方法:

type NoUndefined<T> = T extends undefined ? never : T;
function isNoUndefined <T> (value: T): value is NoUndefined<T> {
return typeof value !== 'undefined'
}
function handleGetter <T>(value:T, operation: () => NoUndefined<T>): NoUndefined<T> {
if (isNoUndefined(value)) return value;
return operation()
}
class CalendarDate extends Date {
#day: number | undefined = undefined
get day () {
return this.#day = handleGetter(this.#day, () => {
console.log('runs once')
return this.getDate()
})
}
}
const c = new CalendarDate()
c.day
c.day
c.day
c.day

一种可能的方法是编写一个函数,用一个新的函数替换现有类原型中的getter,并用智能/自重写/懒惰getter:

function cacheGetter<T>(ctor: { prototype: T }, prop: keyof T): void {
const desc = Object.getOwnPropertyDescriptor(ctor.prototype, prop);
if (!desc) throw new Error("OH NO, NO PROPERTY DESCRIPTOR");
const getter = desc.get;
if (!getter) throw new Error("OH NO, NOT A GETTER");
Object.defineProperty(ctor.prototype, prop, {
get() {
const ret = getter.call(this);
Object.defineProperty(this, prop, { value: ret });
return ret;
}
})
}

因此,如果您调用cacheGetter(Clazz, "key"),它将获得Clazz.prototype.key的属性描述符,并确保它有一个getter。如果任何一步都失败,就会抛出一个错误。否则,它会生成一个新的getter,当被调用时,在当前实例(而不是原型(上调用一次原始getter,然后直接在实例上(同样,不是原型(将属性定义为缓存值。因此,下次在实例上访问属性时,它将使用实例缓存的值,而不是继承的getter。


让我们测试它。在类声明后应用它:

class CalendarDate extends Date {
get day() {
console.log('runs once');
return this.getDate();
}
}
cacheGetter(CalendarDate, "day"); // <-- here

并确保它按预期工作:

const c = new CalendarDate()
console.log(c.day); // runs once, 25
console.log(c.day); // 25
console.log(c.day); // 25
console.log(c.day); // 25

const d = new CalendarDate();
d.setDate(10);
console.log(d.day) // runs once, 10
console.log(c.day) // 25
console.log(d.day) // 10

看起来不错。


如果你不想使用decorator,这是我能想到的最短的方法。我假设decorator方法将以类似的方式实现,作为属性描述符的包装器。

游乐场链接到代码

最新更新