Start with the call, not the function’s location
The keyword this gives a function access to a value associated with its execution. For a regular function, its value usually depends on how the function is called. An arrow function works differently: it does not create its own this and instead uses the value from the surrounding scope where it was defined. Keeping those two rules separate makes most surprises easier to explain.
The examples below can be run individually in a modern browser as ES modules. Module code uses strict mode, so an ordinary call to a regular function does not substitute the global object for a missing this value. Try this small example: function show() { console.log(this); } show(); The output is undefined. Now create an object and call the same kind of function as a property: const box = { show }; box.show(); This time, this is box. The function body did not change; the call did.
Methods depend on how you call them
A regular function used as a method can read properties from the object before the dot in the call. For example: const counter = { count: 2, read() { return this.count; } }; console.log(counter.read()); This logs 2. The expression counter.read() supplies counter as this. Even if a function was originally defined inside an object, however, that location does not permanently attach it to the object.
Extracting the method changes the call: const read = counter.read; read(); In an ES module, this throws a TypeError because read is called without an object, so this is undefined and this.count cannot be read. This is often called “losing context.” It can happen when a method is passed to another function or stored for later. The important question is not where the method came from, but how the saved function is eventually invoked.
Arrow functions capture their surrounding this
An arrow function takes its this from its enclosing scope, even when it is stored on an object. Consider: const counter = { count: 2, read: () => this }; console.log(counter.read()); At the top level of an ES module, the arrow captures undefined, so the call logs undefined rather than counter. Writing an object property with an arrow does not make that arrow a method with its own object-bound this.
That behavior is useful when a callback inside a method needs to keep the method’s this. For example: const counter = { count: 2, makeReader() { return () => this.count; } }; const read = counter.makeReader(); console.log(read()); This logs 2. The regular method call gives makeReader its this; the arrow created inside it retains that value when read is called later. If makeReader had been called without counter, the arrow would have captured a different value instead.
Choose a deliberate fix for callbacks
When passing a method as a callback, decide whether it needs the original object. One option is bind: const counter = { count: 2, read() { return this.count; } }; const read = counter.read.bind(counter); console.log(read()); This logs 2. bind creates a new function whose this is fixed to counter, even when the new function is called on its own. Another option is a wrapper: const readLater = () => counter.read(); The wrapper calls the method through counter each time, so the method receives counter as this.
A regular function’s this can also be supplied for a single invocation with call, as in counter.read.call(counter). That differs from bind, which returns a function for later use. Neither technique changes an arrow function’s captured this. For arrows, choose the enclosing scope deliberately and capture the object there, or refer to the object by name when that is clearer.
Event listeners show why the distinction matters
Browser event listeners offer a practical comparison without needing a page element. Run this in a browser ES module: const source = new EventTarget(); source.addEventListener('ping', function (event) { console.log(this === event.currentTarget); }); source.dispatchEvent(new Event('ping')); It logs true. For this regular listener, the browser calls the function with this set to the event’s current target.
An arrow listener keeps its surrounding this instead: source.addEventListener('ping', (event) => { console.log(this, event.currentTarget === source); }); source.dispatchEvent(new Event('ping')); The arrow logs undefined for this at module top level, followed by true. In an arrow listener, event.currentTarget is an explicit way to access the object handling the event. Remember that event.target can be different when an event travels through an interface; currentTarget identifies the listener’s current receiver. As a rule of thumb, use a regular function when you want the caller-provided this, and an arrow when you want to retain this from an enclosing scope.



