Skip to main content

Functions

7.1 Use named function expressions

Use named function expressions instead of function declarations. eslint: func-style, func-names
Function declarations are hoisted, which means it’s easy — too easy — to reference the function before it is defined in the file. This harms readability and maintainability. If you find that a function’s definition is large or complex enough that it interferes with understanding the rest of the file, then perhaps it’s time to extract it to its own module. Always explicitly name the expression, regardless of whether or not the name is inferred from the containing variable. This eliminates assumptions made about the Error’s call stack.
// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};
The name you give to a function expression appears in stack traces, making debugging easier. It does not need to match the variable name — in fact, a longer descriptive name is preferred.

7.2 Wrap IIFEs in parentheses

Wrap immediately invoked function expressions in parentheses. eslint: wrap-iife
An immediately invoked function expression is a single unit. Wrapping both it and its invocation parens in parentheses cleanly expresses this. Note that in a world with modules everywhere, you almost never need an IIFE.
// immediately-invoked function expression (IIFE)
(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());

7.3 Never declare a function in a non-function block

Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. eslint: no-loop-func
Browsers will allow you to do it, but they all interpret it differently.

7.4 Function declarations are not statements

Note: ECMA-262 defines a block as a list of statements. A function declaration is not a statement.
let test;
if (currentUser) {
  test = () => {
    console.log('Yup.');
  };
}

7.5 Never name a parameter arguments

Never name a parameter arguments. This will take precedence over the arguments object that is given to every function scope.
function foo(name, options, args) {
  // ...
}

7.6 Use rest syntax instead of arguments

Never use arguments. Opt to use rest syntax ... instead. eslint: prefer-rest-params
... is explicit about which arguments you want pulled. Rest arguments are a real Array, not merely Array-like like arguments.
function concatenateAll(...args) {
  return args.join('');
}

7.7 Use default parameter syntax

Use default parameter syntax rather than mutating function arguments.
Mutating function arguments with opts = opts || {} is dangerous — if opts is falsy (e.g., 0 or ''), it will be silently replaced, which can introduce subtle bugs.
// really bad
function handleThings(opts) {
  // No! We shouldn't mutate function arguments.
  // Double bad: if opts is falsy it'll be set to an object which may
  // be what you want but it can introduce subtle bugs.
  opts = opts || {};
  // ...
}

// still bad
function handleThings(opts) {
  if (opts === void 0) {
    opts = {};
  }
  // ...
}

// good
function handleThings(opts = {}) {
  // ...
}

7.8 Avoid side effects with default parameters

Avoid side effects with default parameters.
Default parameters with side effects are confusing to reason about.
let b = 1;
// bad
function count(a = b++) {
  console.log(a);
}
count();  // 1
count();  // 2
count(3); // 3
count();  // 3

7.9 Always put default parameters last

Always put default parameters last. eslint: default-param-last
function handleThings(name, opts = {}) {
  // ...
}

7.10 Never use the Function constructor

Never use the Function constructor to create a new function. eslint: no-new-func
Creating a function this way evaluates a string similarly to eval(), which opens vulnerabilities.
// bad
const add = new Function('a', 'b', 'return a + b');

// still bad
const subtract = Function('a', 'b', 'return a - b');

7.11 Consistent spacing in function signatures

Spacing in a function signature. eslint: space-before-function-paren, space-before-blocks
Consistency is good, and you shouldn’t have to add or remove a space when adding or removing a name.
const x = function () {};
const y = function a() {};

7.12 Never mutate parameters

Never mutate parameters. eslint: no-param-reassign
Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

7.13 Never reassign parameters

Never reassign parameters. eslint: no-param-reassign
Reassigning parameters can lead to unexpected behavior, especially when accessing the arguments object. It can also cause optimization issues, especially in V8.
// bad
function f1(a) {
  a = 1;
  // ...
}

function f2(a) {
  if (!a) { a = 1; }
  // ...
}

// good
function f3(a) {
  const b = a || 1;
  // ...
}

function f4(a = 1) {
  // ...
}

7.14 Prefer spread over apply

Prefer the use of the spread syntax ... to call variadic functions. eslint: prefer-spread
Spread syntax is cleaner, you don’t need to supply a context, and you can’t easily compose new with apply.
const x = [1, 2, 3, 4, 5];
console.log(...x);

new Date(...[2016, 8, 5]);

7.15 Indent multiline function signatures

Functions with multiline signatures or invocations should be indented just like every other multiline list: with each item on a line by itself, with a trailing comma on the last item. eslint: function-paren-newline
function foo(
  bar,
  baz,
  quux,
) {
  // ...
}

console.log(
  foo,
  bar,
  baz,
);

Build docs developers (and LLMs) love