programing

필드를 게으른 것으로 선언

stoneblock 2023. 6. 13. 21:57

필드를 게으른 것으로 선언

TypeScript에서 필드가 게으르게 초기화되었다고 선언하는 구문이 있습니까?

스칼라에 있는 것처럼, 예를 들어:

lazy val f1 = new Family("Stevens")

필드에 처음 액세스할 때만 필드 이니셜라이저가 실행됨을 의미합니다.

더 나은 방법이 필요합니다.

class Lazy {
  private _f1;

  get f1() {
    return this._f1 || (this._f1 = expensiveInitializationForF1());
  }

}

네, 장식가와 함께 이 문제를 해결할 수 있지만, 단순한 경우에는 과잉 살상이 될 수 있습니다.

사용할 수 없습니다.@lazyInitialize직접 타자기로 쳐주세요.그래서 당신은 그것을 다시 써야 합니다.여기 제 장식가가 있습니다. 당신은 그것을 복사해서 사용하기만 하면 됩니다. 대신에 부동산이 아닌 게터에서 @filename을 사용하세요.

@지옥의

const {defineProperty, getPrototypeOf}=Object;
export default function lazy(target, name, {get:initializer, enumerable, configurable, set:setter}: PropertyDescriptor={}): any {
    const {constructor}=target;
    if (initializer === undefined) {
        throw `@lazy can't be set as a property \`${name}\` on ${constructor.name} class, using a getter instead!`;
    }
    if (setter) {
        throw `@lazy can't be annotated with get ${name}() existing a setter on ${constructor.name} class!`;
    }

    function set(that, value) {
        if (value === undefined) {
            value = that;
            that = this;
        }
        defineProperty(that, name, {
            enumerable: enumerable,
            configurable: configurable,
            value: value
        });
        return value;
    }

    return {
        get(){
            if (this === target) {
                return initializer;
            }
            //note:subclass.prototype.foo when foo exists in superclass nor subclass,this will be called
            if (this.constructor !== constructor && getPrototypeOf(this).constructor === constructor) {
                return initializer;
            }
            return set(this, initializer.call(this));
        },
        set
    };
}

시험

describe("@lazy", () => {
    class Foo {
        @lazy get value() {
            return new String("bar");
        }

        @lazy
        get fail(): string {
            throw new Error("never be initialized!");
        }

        @lazy get ref() {
            return this;
        }
    }


    it("initializing once", () => {
        let foo = new Foo();

        expect(foo.value).toEqual("bar");
        expect(foo.value).toBe(foo.value);
    });

    it("could be set @lazy fields", () => {
        //you must to set object to any
        //because typescript will infer it by static ways
        let foo: any = new Foo();
        foo.value = "foo";

        expect(foo.value).toEqual("foo");
    });

    it("can't annotated with fields", () => {
        const lazyOnProperty = () => {
            class Bar {
                @lazy bar: string = "bar";
            }
        };

        expect(lazyOnProperty).toThrowError(/@lazy can't be set as a property `bar` on Bar class/);
    });

    it("get initializer via prototype", () => {
        expect(typeof Foo.prototype.value).toBe("function");
    });

    it("calling initializer will be create an instance at a time", () => {
        let initializer: any = Foo.prototype.value;

        expect(initializer.call(this)).toEqual("bar");
        expect(initializer.call(this)).not.toBe(initializer.call(this));
    });

    it("ref this correctly", () => {
        let foo = new Foo();
        let ref: any = Foo.prototype.ref;

        expect(this).not.toBe(foo);
        expect(foo.ref).toBe(foo);
        expect(ref.call(this)).toBe(this);
    });

    it("discard the initializer if set fields with other value", () => {
        let foo: any = new Foo();
        foo.fail = "failed";

        expect(foo.fail).toBe("failed");
    });

    it("inherit @lazy field correctly", () => {
        class Bar extends Foo {
        }

        const assertInitializerTo = it => {
            let initializer: any = Bar.prototype.ref;
            let initializer2: any = Foo.prototype.ref;
            expect(typeof initializer).toBe("function");
            expect(initializer.call(it)).toBe(it);
            expect(initializer2.call(it)).toBe(it);
        };

        assertInitializerTo(this);
        let bar = new Bar();
        assertInitializerTo({});
        expect(bar.value).toEqual("bar");
        expect(bar.value).toBe(bar.value);
        expect(bar.ref).toBe(bar);
        assertInitializerTo(this);
    });


    it("overriding @lazy field to discard super.initializer", () => {
        class Bar extends Foo {
            get fail() {
                return "error";
            };
        }

        let bar = new Bar();

        expect(bar.fail).toBe("error");
    });

    it("calling super @lazy fields", () => {
        let calls = 0;
        class Bar extends Foo {
            get ref(): any {
                calls++;
                //todo:a typescript bug:should be call `super.ref` getter  instead of super.ref() correctly in typescript,but it can't
                return (<any>super["ref"]).call(this);
            };
        }

        let bar = new Bar();

        expect(bar.ref).toBe(bar);
        expect(calls).toBe(1);
    });

    it("throws errors if @lazy a property with setter", () => {
        const lazyPropertyWithinSetter = () => {
            class Bar{
                @lazy
                get bar(){return "bar";}
                set bar(value){}
            }
        };


        expect(lazyPropertyWithinSetter).toThrow(/@lazy can't be annotated with get bar\(\) existing a setter on Bar class/);

    });
});

최신 버전, 클래스:

class Lazy<T> {
  private #f: T | undefined;
  constructor(#init: () => T) {}
  public get f(): T {
    return this.#f1 ??= this.#init();
  }
}

최신 버전, 인라인:

let value;
// …
use_by_ref(value ??= lazy_init());

생성자에서 다른 값을 반환하는 것이 일반적으로 정의되지 않은 동작으로 간주되기 때문에 프록시가 있는 잠재적 미래 버전이 현재 작동하지 않습니다.

class Lazy<T> {
  constructor(private init: { [K in keyof T]: () => T[K] }) {
    let obj = Object.fromEntries(Object.keys(init).map(k => [k, undefined])) as unknown as { [K in keyof T]: undefined | T[K] };
    Object.seal(obj);
    return new Proxy(obj, this);
  }
  get<K extends keyof T>(t: T, k: K): T[K] {
    return t[k] ??= this.init[k];
  }
}

더 단순화할 수 있습니다.

다음과 같은 것을 사용하고 있습니다.

export interface ILazyInitializer<T> {(): T}

export class Lazy<T> {
private instance: T | null = null;
private initializer: ILazyInitializer<T>;

 constructor(initializer: ILazyInitializer<T>) {
     this.initializer = initializer;
 }

 public get value(): T {
     if (this.instance == null) {
         this.instance = this.initializer();
     }

     return this.instance;
 }
}



let myObject: Lazy<MyObject>;
myObject = new Lazy(() => <MyObject>new MyObject("value1", "value2"));
const someString = myObject.value.getProp;

언급URL : https://stackoverflow.com/questions/42845543/declaring-a-field-as-lazy