Detailed explanation
Arrow functions differ from regular functions mainly in how they handle execution context, not just in their shorter syntax. An arrow captures `this` lexically from its enclosing scope; calling it through an object does not give it that object as `this`. A regular function has its own `this` binding, generally determined by how it is called. For example, `obj.method()` supplies `obj` as the receiver for a regular method. This distinction matters when callbacks need access to the surrounding method’s receiver.
In `const obj = { v: 1, get() { return [1].map(() => this.v); } };`, calling `obj.get()` returns `[1]`. The regular `get` method receives `obj` as `this`, and the arrow callback captures that binding. Replacing the callback with a regular function would not automatically preserve the method’s `this`; you would need an explicit mechanism such as the second argument to `map` or `bind`. Conversely, defining `get` itself as an arrow would capture the enclosing scope’s `this`, not `obj`. An object literal does not create a new `this` scope.
Arrows also have no own `arguments` object or `new.target` binding. References to these resolve through the enclosing lexical environment where available; rest parameters such as `(...args)` are usually clearer for collecting an arrow’s arguments. Arrows cannot be constructors: `new (() => {})()` throws a `TypeError`. They have no own `prototype` property by default, although they still inherit from `Function.prototype`. They cannot be generator functions either. Ordinary function declarations and expressions can be constructors, but not every non-arrow function is constructible: concise object methods and generator functions are important exceptions.
Arrows support implicit returns for expression bodies; block bodies require an explicit `return`. Returning an object literal implicitly requires parentheses, as in `() => ({ v: 1 })`. Interviewers expect you to connect these rules to practical choices: use arrows for callbacks that should retain outer `this`, and regular methods when the receiver should determine `this`. Also remember that `call`, `apply`, and `bind` cannot replace an arrow’s captured `this`, although they can still supply arguments.
Key points to remember
- Arrows capture lexical `this`; regular functions generally receive `this` according to their invocation.
- Arrows have no own `arguments` or `new.target`; use rest parameters for their argument collection.
- Arrows cannot be called with `new` and have no own `prototype` property by default.
- Arrows cannot be generators and support implicit returns only with expression bodies.
- Use arrows for callbacks needing outer `this`, and regular methods for receiver-dependent behaviour.
Common mistakes candidates make
- Assuming an arrow stored as an object property receives that object as `this`.
- Expecting `call`, `apply`, or `bind` to change an arrow’s captured `this`.
- Claiming every regular function is constructible or confusing an arrow’s missing own `prototype` with having no prototype chain.
Likely follow-up questions
Can bind change an arrow function’s this?
No, an arrow resolves `this` through its enclosing lexical scope. `bind` can still create a wrapper that pre-fills arguments, but its `thisArg` cannot override the arrow’s `this`.
What happens when a regular method is detached from its object?
With `const get = obj.get; get();`, the invocation no longer supplies `obj` as the receiver. In strict mode, `this` inside the method is `undefined`, so accessing `this.v` throws; binding the method to `obj` preserves the receiver.
Are arrow functions always unsuitable as class methods?
No: a class field such as `handleClick = () => this.save()` captures the instance’s `this`, which is useful when passing it as a callback. The trade-off is a separate function per instance rather than one shared prototype method.
Interview tip: Start with lexical versus call-dependent `this`, trace the supplied callback example, and then summarise the constructor, arguments, and generator restrictions.
