0
class Simulator {
  constructor() {
    this.gates = Array();
    this.gates.push(new AndGate(200, 200));
  }
  initialize() {
    let canvas = document.getElementById('board');
    canvas.width = 800;
    canvas.height = 500;
    canvas.setAttribute("style", "border: 1px solid black");
    this.gates.push(new AndGate(100, 100));
  }
  run() {
    setTimeout(this.onLoop, 1000);
  }
  onLoop() {
    for (let gate of this.gates) {
      gate.render();
    }
  }
}
let sim = new Simulator();
sim.initialize();
sim.run();

For some reason, the JS transpiled version of my TypeScript class throws an error in the onLoop function. It reports TypeError: this.gates is undefined. However, if I access sim (a Simulator object) and manually access the gates property it's defined. I can run the onLoop code manually from the console.

CertainPerformance
  • 313,535
  • 40
  • 245
  • 254
Bast
  • 72
  • 8

2 Answers2

5

When functions are passed by reference, they lose their reference to this. You're losing this reference when calling setTimeout.

Functions have a bind() method that basically return a new function with a corrected reference to this.

Call it as such:

setTimeout(this.onLoop.bind(this), 1000)

Alternatively, you can also pass an in-line arrow function. Arrow functions don't lose their this context.

setTimeout(() => this.onLoop(), 1000)
Evert
  • 83,661
  • 18
  • 106
  • 170
2

When this.onLoop is called within the setTimeout, the calling context inside onLoop is window, because setTimeout is a function of the window object. You can fix that by using an arrow function that calls onLoop, rather than pass onLoop directly:

class Simulator {
  constructor() {
    this.gates = Array();
    //this.gates.push(new AndGate(200, 200));
  }
  initialize() {
    //let canvas = document.getElementById('board');
    //canvas.width = 800;
    //canvas.height = 500;
    //canvas.setAttribute("style", "border: 1px solid black");
    // this.gates.push(new AndGate(100, 100));
    this.gates.push({ render: () => console.log('rendered') });
  }
  run() {
    setTimeout(() => this.onLoop(), 1000);
  }
  onLoop() {
    for (let gate of this.gates) {
      gate.render();
    }
  }
}
let sim = new Simulator();
sim.initialize();
sim.run();

Or by binding the this context of the onLoop function to the instantiated object:

class Simulator {
  constructor() {
    this.gates = Array();
    //this.gates.push(new AndGate(200, 200));
  }
  initialize() {
    //let canvas = document.getElementById('board');
    //canvas.width = 800;
    //canvas.height = 500;
    //canvas.setAttribute("style", "border: 1px solid black");
    // this.gates.push(new AndGate(100, 100));
    this.gates.push({ render: () => console.log('rendered') });
  }
  run() {
    setTimeout(this.onLoop.bind(this), 1000);
  }
  onLoop() {
    for (let gate of this.gates) {
      gate.render();
    }
  }
}
let sim = new Simulator();
sim.initialize();
sim.run();
CertainPerformance
  • 313,535
  • 40
  • 245
  • 254