Post cover

JavaScript Function Declaration: The 6 Ways

A function is a parametric block of code defined once and called multiple times later.

In JavaScript a function is composed of many parts:

  • JavaScript code that forms the function body
  • The list of parameters
  • The variables accessible from the lexical scope
  • The returned value
  • The context this when the function is invoked
  • Named or an anonymous function
  • The variable that holds the function object
  • arguments object (or missing in an arrow function)

This post shows you six approaches to declare (aka define) JavaScript functions: the syntax, examples, and common pitfalls. Moreover, you will understand when to use a specific function type in certain circumstances.

Before I go on, let me recommend something to you.

The path to becoming good at JavaScript isn't easy... but fortunately with a good teacher you can shortcut.

Take "Modern JavaScript From The Beginning 2.0" course by Brad Traversy to become proficient in JavaScript in just a few weeks. Use the coupon code DMITRI and get your 20% discount!

1. Function declaration

A function declaration (also known as function definition) is made of function keyword, followed by an obligatory function name, a list of parameters in a pair of parenthesis (para1, ..., paramN) and a pair of curly braces {...} that delimits the body code.

An example of a function declaration:


// function declaration
function isEven(num) {
return num % 2 === 0;
}
console.log(isEven(24)); // => true
console.log(isEven(11)); // => false

Open the demo.

function isEven(num) {...} is a function declaration that defines isEven function, which determines if a number is even.

The function declaration creates a variable in the current scope with the identifier equal to the function name. This variable holds the function object.

The function variable is hoisted up to the top of the current scope, which means that the function can be invoked before the declaration (see this chapter for more details).

The created function is named, which means that the name property of the function object holds its name. It is useful when viewing the call stack: in debugging or error message reading.

Let's see these properties in an example:


// Hoisted variable
console.log(hello('Aliens')); // => 'Hello Aliens!'
// Named function
console.log(hello.name) // => 'hello'
// Variable holds the function object
console.log(typeof hello); // => 'function'
function hello(name) {
return `Hello ${name}!`;
}

Open the demo.

The function declaration function hello(name) {...} create a variable hello that is hoisted to the top of the current scope. hello variable holds the function object and hello.name contains the function name: 'hello'.

1.1 A regular function

The function declaration is useful when a regular function is needed. Regular means that you declare the function once and later invoke it in many different places. This is the basic scenario:


function sum(a, b) {
return a + b;
}
console.log(sum(5, 6)); // => 11
console.log([3, 7].reduce(sum)) // => 10

Open the demo.

Because the function declaration creates a variable in the current scope, it is also useful for recursion or detaching event listeners. Contrary to function expressions or arrow functions, that do not create a binding with the function variable by its name.

For example, to calculate recursively the factorial you have to access the function inside:


function factorial(n) {
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
console.log(factorial(4)); // => 24

Open the demo.

Inside factorial() a recursive call is made using the variable that holds the function: factorial(n - 1).

It is possible to use a function expression and assign it to a regular variable, e.g. const factorial = function(n) {...}. But the function declaration function factorial(n) is more compact (no need for const and =).

An important property of the function declaration is its hoisting mechanism. It allows using the function before the declaration in the same scope.

Hoisting is useful in some situations. For example, when you'd like to call the function at the beginning of a script. The function implementation can be located below in the file, so you may not even scroll there.

You can read more details about function declaration hoisting here.

1.2 Difference from function expression

It is easy to confuse the function declaration and the function expression. They look very similar but produce functions with different properties.

An easy-to-remember rule: the function declaration in a statement always starts with the keyword function. Otherwise it's a function expression (see 2.).

The following sample is a function declaration where the statement starts with function keyword:


// Function declaration: starts with "function"
function isNil(value) {
return value == null;
}

In the case of function expressions the JavaScript statement does not start with function keyword (it is somewhere in the middle of the statement code):


// Function expression: starts with "const"
const isTruthy = function(value) {
return !!value;
};
// Function expression: an argument for .filter()
const numbers = ([1, false, 5]).filter(function(item) {
return typeof item === 'number';
});
// Function expression (IIFE): starts with "("
(function messageFunction(message) {
return message + ' World!';
})('Hello');

1.3 Function declaration in conditionals

Some JavaScript environments throw a reference error when invoking a function whose declaration appears within blocks {...} of if, for, or while statements.

Let's enable the strict mode and see what happens when a function is declared in a conditional:


(function() {
'use strict';
if (true) {
function ok() {
return 'true ok';
}
} else {
function ok() {
return 'false ok';
}
}
console.log(typeof ok === 'undefined'); // => true
console.log(ok()); // Throws "ReferenceError: ok is not defined"
})();

Open the demo.

When calling ok(), JavaScript throws ReferenceError: ok is not defined, because the function declaration is inside a conditional block.

The function declaration in conditionals is allowed in non-strict mode, which makes it even more confusing.

As a general rule for these situations, when a function should be created by conditions - use a function expression. Let's see how it is possible:


(function() {
'use strict';
let ok;
if (true) {
ok = function() {
return 'true ok';
};
} else {
ok = function() {
return 'false ok';
};
}
console.log(typeof ok === 'function'); // => true
console.log(ok()); // => 'true ok'
})();

Open the demo.

ok variable is assigned to one or another function depending on the condition. Invoking ok() works fine, without errors.

2. Function expression

A function expression is determined by a function keyword, followed by an optional function name, a list of parameters in a pair of parenthesis (para1, ..., paramN) and a pair of curly braces { ... } that delimits the body code.

Some samples of the function expression:


const count = function(array) { // Function expression
return array.length;
}
console.log(count([5, 7, 8])); // => 3
const methods = {
numbers: [1, 5, 8],
sum: function() { // Function expression
return this.numbers.reduce(function(acc, num) { // func. expression
return acc + num;
});
}
}
console.log(methods.sum()); // => 14

Open the demo.

The function expression creates a function object that can be used in different situations:

  • Assigned to a variable as an object count = function(...) {...}
  • Create a method on an object sum: function() {...}
  • Use the function as a callback array.map(function(...) {...})

The function expression is the working horse in JavaScript. Usually, you deal with this type of function declaration, alongside the arrow function (if you prefer short syntax and lexical context).

2.1 Named function expression

A function is anonymous when it does not have a name (name property is an empty string ''):


const name = (function(variable) {return typeof variable; }).name
console.log(name); // => ''

Open the demo.

This is an anonymous function, which name is an empty string.

Sometimes the function name can be inferred. For example, when the anonymous is assigned to a variable:


const myFunctionVar = function(variable) {
return typeof variable;
};
console.log(myFunctionVar.name); // => 'myFunctionVar'

Open the demo.

The anonymous function name is 'myFunctionVar', because myFunctionVar variable name is used to infer the function name.

When the expression has the name specified, this is a named function expression. It has some additional properties compared to simple function expression:

  • A named function is created, i.e. name property holds the function name
  • Inside the function body a variable with the same name holds the function object

Let's use the above example, but set a name in the function expression:


const myFunctionVar = function getNumber() {
console.log(typeof funName === 'function'); // => true
return 42;
}
console.log(myFunctionVar()); // => 42
console.log(myFunctionVar.name); // => 'getNumber'
console.log(typeof getNumber); // => 'undefined'

Open the demo.

function getNumber() {...} is a named function expression. The variable getNumber is accessible within function scope, but not outside. Either way, the property name of the function object holds the name: getNumber.

2.2 Favor named function expression

When a function expression const fun = function() {} is assigned to a variable, some engines infer the function name from this variable. However, callbacks might be passed as anonymous function expressions, without storing into variables: so the engine cannot determine its name.

It is reasonable to favor named functions to anonymous ones to gain benefits like:

  • The error messages and call stacks show more detailed information when using the function names
  • More comfortable debugging by reducing the number of anonymous stack names
  • The function name says what the function does
  • You can access the function inside its scope for recursive calls or detaching event listeners

3. Shorthand method definition

Shorthand method definition is used in a method declaration on object literals and ES2015 classes. You can define them using a function name, followed by a list of parameters in a pair of parenthesis (para1, ..., paramN) and a pair of curly braces { ... } that delimits the body statements.

The following example uses a shorthand method definition in an object literal:


const collection = {
items: [],
add(...items) {
this.items.push(...items);
},
get(index) {
return this.items[index];
}
};
collection.add('C', 'Java', 'PHP');
console.log(collection.get(1)); // => 'Java'

Open the demo.

add() and get() methods in collection object are defined using short method definition. These methods are called as usual: collection.add(...) and collection.get(...).

The short approach of method definition has several benefits over traditional property definition with a name, colon : and a function expression add: function(...) {...}:

  • A shorter syntax is easier to understand
  • Shorthand method definition creates a named function, contrary to a function expression. It is useful for debugging.

The class syntax requires method declarations in a short form:


class Star {
constructor(name) {
this.name = name;
}
getMessage(message) {
return this.name + message;
}
}
const sun = new Star('Sun');
console.log(sun.getMessage(' is shining')); // => 'Sun is shining'

Open the demo.

3.1 Computed property names and methods

ECMAScript 2015 adds a nice feature: computed property names in object literals and classes.
The computed properties use a slight different syntax [methodName]() {...}, so the method definition looks this way:


const addMethod = 'add';
const getMethod = 'get';
const collection = {
items: [],
[addMethod](...items) {
this.items.push(...items);
},
[getMethod](index) {
return this.items[index];
}
};
collection[addMethod]('C', 'Java', 'PHP');
console.log(collection[getMethod](1)); // => 'Java'

Open the demo.

[addMethod](...) {...} and [getMethod](...) {...} are shorthand method declarations with computed property names.

4. Arrow function

An arrow function is defined using a pair of parenthesis that contains the list of parameters (param1, param2, ..., paramN), followed by a fat arrow => and a pair of curly braces {...} that delimits the body statements.

When the arrow function has only one parameter, the pair of parentheses can be omitted. When it contains a single statement, the curly braces can be omitted too.

Let's see the arrow function basic usage:


const absValue = (number) => {
if (number < 0) {
return -number;
}
return number;
}
console.log(absValue(-10)); // => 10
console.log(absValue(5)); // => 5

Open the demo.

absValue is an arrow function that calculates the absolute value of a number.

The function declared using a fat arrow has the following properties:

  • The arrow function does not create its execution context but takes it lexically (contrary to function expression or function declaration, which creates its own this depending on invocation)
  • The arrow function is anonymous. However, the engine can infer its name from the variable holding the function
  • arguments object is not available in the arrow function (contrary to other declaration types that provide arguments object). You are free to use rest parameters (...params), though.

4.1 Context transparency

this keyword is a confusing aspect of JavaScript (check this article for a detailed explanation of this).

Because functions create their execution context, often it is difficult to detect this value.

ECMAScript 2015 improves this usage by introducing the arrow function, which takes the context lexically (or simply uses this from the immediate outer scope). This is nice because you don't have to use myFunc.bind(this) or store the context const self = this when a function needs the enclosing context.

Let's see how this is accessed from the outer function:


class Numbers {
constructor(array) {
this.array = array;
}
addNumber(number) {
if (number !== undefined) {
this.array.push(number);
}
return (number) => {
console.log(this === numbersObject); // => true
this.array.push(number);
};
}
}
const numbersObject = new Numbers([]);
const addMethod = numbersObject.addNumber();
addMethod(1);
addMethod(5);
console.log(numbersObject.array); // => [1, 5]

Open the demo.

Numbers class holds an array of numbers and provides a method addNumber() to insert new numbers.
When addNumber() is called without arguments, a closure is returned that allows inserting numbers. This closure is an arrow function that has this as numbersObject instance because the context is taken lexically from addNumbers() method.

Without the arrow function, you have to manually fix the context. It means using workarounds like array.bind(thisVal) method:


//...
return function(number) {
console.log(this === numbersObject); // => true
this.array.push(number);
}.bind(this);
//...

or store the context into a separate variable var self = this:


//...
const self = this;
return function(number) {
console.log(self === numbersObject); // => true
self.array.push(number);
};
//...

Context transparency can be used when you want to keep this as is, taken from the enclosing context.

4.2 Short callbacks

When creating an arrow function, the parenthesis pairs and curly braces are optional for a single parameter and single body statement. This helps in creating very short callback functions.

Let's make a function that finds if an array contains 0:


const numbers = [1, 5, 10, 0];
console.log(numbers.some(item => item === 0)); // => true

Open the demo.

item => item === 0 is an arrow function that looks straightforward.

Note that nested short arrow functions are difficult to read. The convenient way to use the shortest arrow function form is a single callback (without nesting).

If necessary, use the expanded syntax of arrow functions when writing nested arrow functions. It's just easier to read.

5. Generator function

The generator function in JavaScript returns a Generator object. Its syntax is similar to function expression, function declaration, or method declaration, just that it requires a star character *.

The generator function can be declared in the following forms:

a. Function declaration form function* <name>():


function* indexGenerator(){
var index = 0;
while(true) {
yield index++;
}
}
const g = indexGenerator();
console.log(g.next().value); // => 0
console.log(g.next().value); // => 1

Open the demo.

b. Function expression form function* ():


const indexGenerator = function* () {
let index = 0;
while(true) {
yield index++;
}
};
const g = indexGenerator();
console.log(g.next().value); // => 0
console.log(g.next().value); // => 1

Open the demo.

c. Shorthand method definition form *<name>():


const obj = {
*indexGenerator() {
var index = 0;
while(true) {
yield index++;
}
}
}
const g = obj.indexGenerator();
console.log(g.next().value); // => 0
console.log(g.next().value); // => 1

Open the demo.

In all 3 cases, the generator function returns the generator object g. Later g is used to generate a series of incremented numbers.

6. One more thing: new Function

In JavaScript functions are first-class objects: a function is a regular object of type function.

The ways of the declaration described above create the same function object type. Let's see an example:


function sum1(a, b) {
return a + b;
}
const sum2 = function(a, b) {
return a + b;
}
const sum3 = (a, b) => a + b;
console.log(typeof sum1 === 'function'); // => true
console.log(typeof sum2 === 'function'); // => true
console.log(typeof sum3 === 'function'); // => true

Open the demo.

The function object type has a constructor: Function.

When Function is invoked as a constructor new Function(arg1, arg2, ..., argN, bodyString), a new function is created. The arguments arg1, args2, ..., argN passed to the constructor become the parameter names for the new function, and the last argument bodyString is used as the function body code.

Let's create a function that sums two numbers:


const sumFunction = new Function('numberA', 'numberB',
'return numberA + numberB'
);
console.log(sumFunction(10, 15)); // => 25

Open the demo.

sumFunction created with Function constructor invocation has parameters numberA and numberB and the body return numberA + numberB.

The functions created this way don't have access to the current scope, thus closures cannot be created. They are always created in the global scope.

One possible application of new Function is a better way to access the global object in a browser or NodeJS script:


(function() {
'use strict';
const global = new Function('return this')();
console.log(global === window); // => true
console.log(this === window); // => false
})();

Open the demo.

Remember that functions seldom should be declared using new Function(). Because the function body is evaluated on runtime, this approach inherits many eval() usage problems: security risks, harder debugging, no way to apply engine optimizations, no editor auto-complete.

7. In the end, which way is better?

There is no winner or loser. The decision of which declaration type to choose depends on the situation.

There are some rules however that you may follow in common situations.

If the function uses this from the enclosing function, the arrow function is a good solution. When the callback function has one short statement, the arrow function is a good option too, because it creates short and light code.

For a shorter syntax when declaring methods on object literals, the shorthand method declaration is preferable.

new Function way to declare functions normally should not be used. Mainly because it opens potential security risks, doesn't allow code auto-complete in editors, and loses the engine optimizations.

Do you prefer arrow functions or function expressions?!

Like the post? Please share!

Dmitri Pavlutin

About Dmitri Pavlutin

Software developer and sometimes writer. My daily routine consists of (but not limited to) drinking coffee, coding, writing, overcoming boredom 😉. Living in the sunny Barcelona. 🇪🇸