In JavaScript, this is decided by how a function is called, not where it was written. service.list() sets this to service. But const fn = service.list copies only the function. When the router or a timer later calls fn(), there is no object before the dot. In a class (always strict mode) this is undefined.
Three fixes:
- Arrow wrapper at the call site:
app.get("/users", (req, res) => service.list(req, res)). Clear and local. My default. - Bind once in the constructor:
this.list = this.list.bind(this). Every caller is safe, but it is easy to forget for new methods. - Arrow class field:
list = (req, res) => { ... }. Always bound, but it is created per instance, is not on the prototype, and can't be called withsuper.
The same bug appears with setTimeout(this.flush, 1000), items.map(this.format) and event listeners. Frameworks like NestJS avoid it, because they call your methods on the instance themselves.
thisis set by the call site, not by where the method is defined.- Passing
service.methoddetaches the function from its object. - Class code is strict, so a detached call gets
this === undefined. - Fixes are an arrow wrapper,
bindin the constructor, or an arrow class field. - Each fix has trade-offs, and an arrow wrapper at the call site is the most explicit.