this is decided by how a function is called, not where it was defined. c.increment() calls it as a method of c, so this is c. Passing c.increment hands over the bare function and loses the object entirely — the listener calls it with no receiver, and in a class body (which is always strict mode) that leaves this as undefined rather than the global object.
Three ways out, in the order I would reach for them:
// 1. Arrow wrapper at the call site — explicit, no surprises.
button.addEventListener('click', () => c.increment());
// 2. Class field — binds once per instance, good when the method is
// passed around in several places.
class Counter {
count = 0;
increment = () => { this.count++; };
}
// 3. bind — fine, but note it returns a NEW function, so keep the
// reference if you ever want to removeEventListener it.
const handler = c.increment.bind(c);
button.addEventListener('click', handler);
button.removeEventListener('click', handler);
That last point catches people out: removeEventListener('click', c.increment.bind(c)) never removes anything, because it is a different function object each time.
Small addition to option 2 — a class field is per instance, not per class, so a thousand instances is a thousand functions. Irrelevant for a handful of components, worth knowing if you are creating them in a loop over a large list.