html - nästlad funktion kan inte hitta sig själv i Typescript


Jag har följande kod:

//Main Entry Point.
start() {
    this.init();
    this.gameLoop();
}

//Init, runs only once.
init() {
    let initalEntity1: Entity = new Entity(10, 10, Gender.female);
    let initalEntity2: Entity = new Entity(60, 60, Gender.male);
    console.log("There are " + this.Entities.length + " Items in the entities Array")
    this.Entities.push(initalEntity1, initalEntity2);
    console.log("There are " + this.Entities.length + " Items in the entities Array")
}

gameLoop() {
    console.log("Performing a Game Loop");
    requestAnimationFrame(this.gameLoop);

    //MAIN LOOP THROUGH ENTITIES
    for (let i in this.Entities) {
            this.Render.drawEntitysToScreen(this.Entities[i].EntityPosition, this.Entities[i].gender);
        }
}

Den går in i start() utan problem, och utför också all funktionalitet i init(). Därefter fortsätter den till gameloop() som den kommer att köra en gång, men raden requestAnimationFrame(this.gameLoop); som är avsedd att trigga funktionen att köras igen som en Canvas-ram orsakar följande fel:

TypeError: this är odefinierad

försöker requestAnimationFrame(gameLoop); men det får TypeScript-kompilatorn att bli upprörd...

Svar #1

Detta beror på hur this-bindning fungerar i JavaScript. Sättet på vilket du skickar this.gameLoop till requestAnimationFrame är i princip att skicka en obunden gameLoop-funktion, och när den anropas har den därför förlorat referensen till sitt this.

Det finns ett antal möjliga lösningar på detta problem:

  1. Du kan binda this.gameLoop till this inne i klasskonstruktören, på följande sätt:

    constructor() {
      this.gameLoop = this.gameLoop.bind(this);
    }
    
  2. Du kan binda this.gameLoop till this som en del av gameLoop-metodddefinitionen. Istället för att definiera gameLoop som

    gameLoop() {
    

    Om du istället använder

    gameLoop = () => {
    

    det kommer automatiskt att bindas till this. Detta är en egenskap av att använda fet pil för funktionsdeklarationer: det utför automatiskt bindning till this som finns vid funktionsdeklarationen.

  3. Du kan ändra hur du skickar gameLoop till requestAnimationFrame:

    requestAnimationFrame(() => this.gameLoop());
    

    Detta utnyttjar återigen den automatiska this-bindningen som utförs av pilfunktionen, men istället för att göra det som en del av klassmetoddeklarationen kan du helt enkelt göra det senare vid den tidpunkt du behöver att den ska bindas.

    Observera dock att att göra det på detta sätt innebär att en ny funktion kommer att skapas varje gång gameLoop anropas.

Svar #2

När du anropar this.gameLoop() inom start, kommer värdet för this inuti gameLoop's kropp att vara den klass gameLoop tillhör, eftersom du anropar gameLoop som en egenskap av this (klassen).

När du skickar en funktionsreferens kan dess värde för this vara vad som helst när funktionen anropas från någon annanstans.

Lösning 1 | Använda Function.prototype.bind

“bind värdet för this när du ger funktionen till requestAnimationFrame. Genom att göra detta säger du uttryckligen:

Låt argumentet till bind vara this inom varje anrop till gameLoop, oavsett hur det anropas eller varifrån det anropas.

requestAnimationFrame(this.gameLoop.bind(this));

Observera att bind returnerar en ny funktion, så den ursprungliga gameLoop-funktionen som fortfarande är en egenskap i din klass förblir oförändrad.

Lösning 2 | Använda en pilfunktion

Definiera en pilfunktion för att slutligen utföra anropet till gameLoop istället för requestAnimationFrame. this-värdet inom pilfunktioner är statiskt och ärvt från exekveringskontexten som omger funktionsdeklarationen.

requestAnimationFrame(() => this.gameLoop());

Relaterade artiklar