Function Parameters & Namespaces
|
This section documents modern ECMAScript and core browser JavaScript APIs — it is not tied to any specific framework or library (React, Vue, Angular, etc.). This content was generated with the assistance of AI and should be verified against the current ECMAScript specification and MDN documentation before relying on it in production, since JavaScript language features and browser API support continue to evolve. This section’s bibliography lists the reference material consulted while preparing these pages. |
JavaScript places no restrictions on how a function is invoked relative to its declared parameter list — you can call a function with fewer arguments than it declares, more arguments than it declares, and (via destructuring) with a single object or array argument that gets unpacked into several named parameters. This page covers the parameter-list features that make this flexible, and closes with a legacy pattern — using a function purely as a scoping device, or "namespace" — that modern module syntax has superseded.
Default Parameter Values
Follow a parameter name with = <expression> to give it a default value, used whenever the caller omits
that argument (or passes undefined explicitly):
function getPropertyNames(o, a = []) {
for (const property in o) a.push(property);
return a;
}
getPropertyNames({x: 1}); // => ["x"], `a` defaults to a fresh array
The default expression is evaluated at call time, not at definition time — so a = [] creates a brand
new array on every invocation that omits a, rather than sharing one array across calls. Because the
expression is re-evaluated per call, it can also be a variable, a function call, or even reference an
earlier parameter in the same list:
const rectangle = (width, height = width * 2) => ({width, height});
rectangle(1); // => { width: 1, height: 2 }
Put every parameter that has a default at the end of the parameter list. JavaScript has no way to name
arguments positionally, so a caller cannot skip the first parameter to supply only the second — they would
have to pass undefined explicitly for it.
Rest Parameters
Where default parameters let a function be called with fewer arguments than declared, a rest
parameter lets it be called with arbitrarily more. Prefix the last parameter with … to collect every
remaining argument into a real array:
function max(first = -Infinity, ...rest) {
let maxValue = first;
for (const n of rest) {
if (n > maxValue) maxValue = n;
}
return maxValue;
}
max(1, 10, 100, 2, 3, 1000, 4, 5, 6); // => 1000
A rest parameter must be the last parameter in the list, and its value is always an array — possibly
empty, but never undefined. A function that accepts any number of arguments this way is commonly called
variadic.
Don’t confuse the … that collects arguments into a rest parameter (in a function definition) with
the … spread syntax that expands an array back out into individual arguments at a call site (fn(…args))
or into individual elements inside an array/object literal — see Arrays &
Typed Arrays and Objects & Destructuring.
|
Destructured Parameters
Because parameter binding works like an assignment, the same destructuring patterns covered in Objects & Destructuring can be applied directly in a parameter list, unpacking an array or object argument into several named parameters:
// Array destructuring: unpack a two-element array into named parameters
function vectorAdd([x1, y1], [x2, y2]) {
return [x1 + x2, y1 + y2];
}
vectorAdd([1, 2], [3, 4]); // => [4, 6]
// Object destructuring: unpack an object's properties into named parameters
function vectorMultiply({x, y, z = 0}, scalar) {
return {x: x * scalar, y: y * scalar, z: z * scalar};
}
vectorMultiply({x: 1, y: 2}, 2); // => {x: 2, y: 4, z: 0}
Destructured object parameters double as a stand-in for named/keyword arguments, which JavaScript doesn’t support directly — useful once a function has enough optional parameters that positional order becomes hard to remember:
function arrayCopy({from, to = from, n = from.length, fromIndex = 0, toIndex = 0}) {
const values = from.slice(fromIndex, fromIndex + n);
to.splice(toIndex, 0, ...values);
return to;
}
const a = [1, 2, 3, 4, 5];
const b = [9, 8, 7, 6, 5];
arrayCopy({from: a, n: 3, to: b, toIndex: 4}); // => [9, 8, 7, 6, 1, 2, 3, 5]
A destructured array or object parameter can itself carry a rest element (…coords] inside [x, y, …coords],
or …rest } inside an object pattern), independent of the function’s own top-level rest parameter, and
destructuring can nest to any depth — though beyond two or three levels it usually reads more clearly to
destructure explicitly inside the function body instead.
The arguments Object (Legacy)
|
See also Legacy Features to Avoid. Before rest parameters were
introduced in ES6, variadic functions were written using the |
Inside any non-arrow function body, the identifier arguments refers to an array-like object holding every
argument the function was actually called with, indexable by position:
function max(x) {
let maxValue = -Infinity;
for (let i = 0; i < arguments.length; i++) {
if (arguments[i] > maxValue) maxValue = arguments[i];
}
return maxValue;
}
arguments is array-like (it has a length and numeric indices) but is not a real Array, so array
methods like map/filter aren’t available on it directly without Array.from(arguments). It also carries
historical baggage that makes it hard for engines to optimize, and in strict mode arguments is a reserved
word — you cannot declare a parameter or local variable with that name. When you encounter it in older
code, it can almost always be replaced with a …args rest parameter, which is a real array, plays well
with arrow functions (arrow functions have no arguments of their own — they inherit it from the enclosing
non-arrow function, which is rarely what’s intended), and communicates the function’s variadic intent right
in its signature.
Namespacing with Functions (Legacy)
|
This pattern is a pre-ES6 workaround. Prefer ES modules, covered in Modules, for organizing code today — every module already has its own top-level scope, so this trick is no longer needed to avoid polluting the global namespace. |
Variables declared inside a function body are invisible outside it. Before ES modules existed, code that needed to be shared across multiple pages or scripts — but that also declared its own intermediate variables — exploited this by wrapping the whole chunk of code in a function purely to keep those variables out of the global scope:
function chunkNamespace() {
// Any variable declared here is local to this function,
// instead of leaking into the global namespace.
}
chunkNamespace(); // must be invoked, or nothing inside it runs
Naming and separately invoking the function still leaves one global name behind (chunkNamespace itself).
The immediately invoked function expression (IIFE) pattern avoids even that, by defining and calling an
anonymous function in a single expression:
(function () {
// Fully private scope: nothing declared here is visible outside,
// and no name is left behind in the global namespace either.
const secret = compute();
expose(secret);
})();
This "IIFE namespace" pattern was, for years, the standard way JavaScript libraries avoided naming collisions with the pages that embedded them. It has been superseded by ES modules (each module file gets its own top-level scope automatically, with no wrapping function or invocation required) and is covered here primarily so it’s recognizable when encountered in older code — see Modules for the modern replacement.