Functions Every Way
Declarations, expressions, arrows, defaults, rest, spread.
Functions are how you give a name to a chunk of behavior so you can reuse it. JS has, conservatively, three and a half ways to write one. They look similar, but each has a slightly different personality. Knowing which to pick is most of the battle.
Three ways to make a function
// 1. Function declaration
function add(a, b) {
return a + b;
}
// 2. Function expression
const subtract = function (a, b) {
return a - b;
};
// 3. Arrow function
const multiply = (a, b) => a * b;
// All three work the same when called:
add(2, 3); // 5
subtract(5, 1); // 4
multiply(3, 4); // 12The differences only show up at the edges: how they hoist, what they do with this, and how they read.
Hoisting: declarations move up
Function declarationsare hoisted, meaning you can call them before they appear in your file. Expressions and arrows aren't:
hello(); // Works - hoisted
function hello() {
console.log("hi");
}
hi(); // ReferenceError - temporal dead zone
const hi = () => console.log("hi");Arrow functions: shorter and quieter
Arrows have a compact syntax for short functions:
// Full form
const add = (a, b) => {
return a + b;
};
// Single expression - return is implicit, no braces
const add = (a, b) => a + b;
// Single param - parens optional (linters usually want them)
const double = n => n * 2;
// Returning an object literal? Wrap in parens
const wrap = n => ({ value: n }); // not { value: n }, that's a blockthis: the one real difference
Regular functions have their own this. Arrow functions don't - they inherit this from where they were defined. This matters most inside object methods and callbacks:
const counter = {
count: 0,
// Regular function: 'this' is the counter
increment() {
this.count++;
},
// Inside a callback, regular functions LOSE 'this'
startBroken() {
setInterval(function () {
this.count++; // 'this' is undefined or window here
}, 1000);
},
// Arrow callbacks keep the outer 'this'
startFixed() {
setInterval(() => {
this.count++; // 'this' is still the counter
}, 1000);
},
};this to be the object. Use shorthand methods like increment() {} instead.Default parameters
function greet(name = "stranger", greeting = "Hello") {
return greeting + ", " + name + "!";
}
greet(); // "Hello, stranger!"
greet("Ada"); // "Hello, Ada!"
greet("Ada", "Howdy"); // "Howdy, Ada!"
// Defaults only kick in for undefined (not null)
greet(undefined, "Hi"); // "Hi, stranger!"
greet(null, "Hi"); // "Hi, null!"Rest parameters: catch all the extras
// ...args collects every remaining argument into an array
function sum(...nums) {
return nums.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
// Can mix with named params
function tagged(tag, ...items) {
return tag + ": " + items.join(", ");
}
tagged("Fruits", "apple", "pear", "kiwi");
// "Fruits: apple, pear, kiwi"Spread: the other ...
Same dots, opposite direction. ... in a function signature collects; in a call (or array/object literal) it spreads:
const nums = [1, 2, 3, 4];
Math.max(...nums); // 4 (spreads to Math.max(1, 2, 3, 4))
const combined = [0, ...nums, 5]; // [0, 1, 2, 3, 4, 5]
const copy = [...nums]; // shallow clone
const a = { x: 1 };
const b = { ...a, y: 2 }; // { x: 1, y: 2 }Try them all live
Quiz
Which kind of function is safe to call before its definition?
Recap
- Function declarations hoist. Arrows and function expressions don't.
- Arrows inherit
thisfrom the enclosing scope. Regular functions get their own. - Default params catch
undefined(notnull). ...restcollects into an array in a signature....spreadunpacks in calls and literals.- When in doubt: short helper or callback → arrow. Object method or something that needs
this→ shorthand method.