仅在 Typescript 中运行一次装饰代码的访问器装饰器,returns 先前在后续调用中计算的值

Accessor decorator that only runs decorated code once in Typescript, returns previously calculated value on subsequent calls

这是 的衍生作品。

我在打字稿 class 上有一些 get 方法,计算量很大。对于其中一些,我可以确定结果每次都是相同的——它不会根据 class 实例的状态而改变。但是,该方法在代码中的单个实例上被多次调用。在这方面,只有第一次实际 运行 计算才有意义,而在随后的时间里则没有。例如:

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;
    }
});

第一次访问cell.cosine,实际上是运行s计算量大的代码,并将结果赋值给私有的属性_cosine。随后的每一次,它只是 return 那个值。如果你 运行 这段代码,你会看到任何 cell.count 只有 1,即使每个实例访问 cell.cosine 100 次。

装饰师能做到吗?

这样做的缺点是,对于每个 属性 我希望应用此一次性逻辑,我需要声明一个 private _property,并包含那一点点逻辑。有一个 to the question of decorating a method such that it only runs once. User plumbn adapted this to work with getters。但是,该答案不适用于 getter/accessor 的这种情况,因为 getter 每次调用时都需要 return 一个值.

是否可以为 get 方法编写装饰器,使其仅在第一次调用时真正 运行s(并且 returns 值),并且在随后的时间里,它returns 最初计算的值没有 运行ning 再次计算?

我知道你在想什么...

您可能会问“为什么不直接在构造函数中赋值?”这是一个很好的问题。如果可以,你应该这样做,因为它很大 improvement in performance, as this jsbench shows。然而,除了丑陋之外,在某些情况下这是行不通的。例如

class Cell {
    angle: position;
    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;
    }
}

在这种情况下,调用 cell.neighbor 方法会生成 8 个新 Cell。如果我在构造函数中分配 this.neighbors,那么每个新生成的 Cell 都会在 its 构造函数中调用 this.neighbors,我们就有了一个无限循环。在这种情况下,cell.neighbors return 每次访问时都具有相同的值,但是每次调用时 运行 该代码都是一种浪费。我可以使用 if (this._neighbors) { return this._neighbors } else { this._neighbors = run_my_code(); return this._neighbors } 模式,但在装饰器中捕获此行为似乎更优雅。我在上面发布的 jsbench 显示执行此操作时性能略有提高(不如在构造函数中声明它好,但仍然是一个改进)。

我相信我们可以第一次计算值并存储它,然后在后续调用中使用 reflect-metadata 检索它,如下所示。

import "reflect-metadata";

const metadataKey = Symbol("initialized");

function once(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const getter = descriptor.get!;
  descriptor.get = function () {
    const val = Reflect.getMetadata(metadataKey, target, propertyKey);

    if (val) {
      return val;
    }

    const newValue = getter.apply(this);
    Reflect.defineMetadata(metadataKey, newValue, target, propertyKey);
    return newValue;
  };
}

可以找到一个工作示例 here

非常感谢 OP 帮助解决了问题。