6. Functions in depth: closures, this, call/apply/bind
Four ways to write a function, with real behavioral differences:
function declared() {} // hoisted fully — usable before its definition
const expr = function() {}; // hoisted as `undefined`, only usable after assignment
const arrow = () => {}; // no own `this`, `arguments`, or `prototype`
class C { method() {} } // method shorthand, not enumerable, strict mode always
this is the single most common source of JS confusion. It is NOT
lexically scoped for regular functions — it's determined by how the
function is called, not where it's defined:
const obj = {
name: "obj",
regular: function () { return this.name; },
arrow: () => { return this?.name; }, // `this` here is captured from the ENCLOSING scope
};
obj.regular(); // "obj" — called as obj.regular(), so `this` = obj
const fn = obj.regular;
fn(); // undefined (or throws in strict mode) — called bare, `this` is undefined
obj.arrow(); // undefined — arrow functions never bind their own `this`
call/apply/bind explicitly control this:
function greet(greeting) { return `${greeting}, ${this.name}`; }
const person = { name: "Ada" };
greet.call(person, "Hi"); // "Hi, Ada" — invoke immediately, args listed individually
greet.apply(person, ["Hi"]); // "Hi, Ada" — invoke immediately, args as an array
const bound = greet.bind(person);
bound("Hi"); // "Hi, Ada" — returns a NEW function permanently bound to `person`
Real-world example. React class components historically needed
this.handleClick = this.handleClick.bind(this)in the constructor for exactly this reason — passingthis.handleClickas a callback strips it fromthis.Hooks and arrow-function class fields made this mostly historical, but the underlyingthis-binding rule hasn't changed.