# Functions, closures, and context
# Declaration or expression?
A function declaration creates a named function in its scope. A function expression creates a function value that can be assigned, passed, or returned.
// Declaration
function add(a, b) {
return a + b;
}
// Function expression
const multiply = function (a, b) {
return a * b;
};
// Arrow-function expression
const square = value => value * value;
Declarations are useful for named operations. Expressions are useful when a function is data: for example, a callback passed to addEventListener, map, or filter. Arrow functions have lexical this and no own arguments object; they are not just shorter syntax in every situation.
# Hoisting
Before executing a scope, JavaScript initializes declarations according to their kind:
sayHello(); // works: declaration is initialized before this line
function sayHello() {
console.log('Hello');
}
console.log(typeof oldValue); // "undefined"
var oldValue = 1;
// useValue(); // ReferenceError: temporal dead zone
const useValue = () => 2;
Function declarations can generally be called before their source line. A var binding exists early but contains undefined until assignment, so a var function expression is not callable before assignment. let and const bindings are unavailable in the temporal dead zone until initialization; function and arrow expressions are therefore not callable before their variable is initialized.
Hoisting describes initialization behavior; source code is not physically moved. Prefer defining functions before using them so control flow is easy to read.
# Closures
A closure is a function together with access to variables from its lexical environment. The inner function can keep using those variables after the outer function returns:
function createCounter() {
let count = 0;
return () => ++count;
}
const next = createCounter();
next(); // 1
next(); // 2
let creates a new block binding for each loop iteration, so closures can retain the correct item:
for (let number = 1; number <= 3; number += 1) {
setTimeout(() => console.log(number), 0);
}
With var, handlers share one function-scoped binding. A factory or IIFE was a common older workaround; the let binding above is the modern equivalent.
A closure remembers lexical variables; it does not automatically snapshot every external value or DOM property.
# Context and this
Closure scope and call context are different:
- A closure determines which lexical variables a function can access.
thisis selected by how a regular function is called.
const user = {
name: 'Ada',
regular() { return this.name; },
arrow: () => this.name
};
user.regular(); // "Ada"
user.arrow(); // does not use user as its this
An arrow function captures this from its surrounding scope and cannot define its own call context. A regular DOM event listener commonly receives the element as this, while an arrow listener keeps the surrounding this; use event.currentTarget when the element should be explicit.
| Concept | What it controls |
|---|---|
let/const scope | Where a binding is available |
| Closure | Which lexical bindings a function retains |
Regular-function this | Context selected at the call site |
Arrow-function this | Lexical context captured from outside |
Use regular functions when dynamic this, a constructor, or an own arguments object is needed. Use arrows for callbacks when lexical context is what you want.
# Exercises
- Write a function factory that returns a click handler with a private counter.
- Rewrite a
varloop usingletand explain why the handlers differ. - Attach both a regular-function and an arrow-function event listener and inspect
thisandevent.currentTarget.