Defining functions
What are functions?
Functions are the fundamental building blocks of most computer programs. They can be viewed as "subprograms" within the overall program. Like the program itself, a function is a set of statements that performs a task. A function can be called or invoked, and in that way executed, an arbitrary number of times. A function is usually called from outside the function, but it can also be called from inside the function (the function calls itself), in which case we speak of recursion. Code that performs a certain task, especially a task that might be needed at multiple places in the code, should be captured by a function. This provides a clear, compact and well maintainable program.
Functions take some input and return an output. The function establishes some relationship between the input and the output;
it produces an output for each input.
However, in programming languages functions usually extend beyond the strict mathematical definition of a function and include every
subroutine: a sequence of instructions that performs a specific task,
also when there are no inputs or outputs.
In JavaScript, the optional input values, the arguments, in a function call are passed to the function's optional parameters.
The output value is specified by a return
statement. A function without a return statement will return the default output value undefined
.
// calls the function 'add' and passing arguments 2 and 3 to the function's parameters a and b:
console.log(add(2,3)); // logs: 5
// function with parameters a and b:
function add(a,b) {
return a + b;
};
let foo = 0;
// calls the function 'addOneTofoo':
console.log(addOneTofoo()); // logs: undefined
console.log(foo); // logs: 1
// function with no parameters and no return statement:
function addOneTofoo() {
foo++;
};
In JavaScript, functions are objects, although the typeof
operator returns "function". They can have properties and methods just like any other object.
You can even create a function with the Function
constructor (using the new
operator),
but this should not be practiced because of security and performance issues.
Unlike all other objects, functions can be called.
function greeting() { console.log("Hello World!"); };
greeting(); // logs: "Hello World!"
console.log(typeof greeting); // logs: "function"
// Inherited properties and methods:
console.log(greeting.length); // logs: 0 // The number of parameters
// Returns a string representing the source code of the function:
console.log(greeting.toString()); // logs: "function greeting() { console.log("Hello World!"); }"
// Don't do this:
const f = new Function('console.log("Hello World!");');
f(); // logs: "Hello World!"
Defining functions
Function declarations
The function declaration (function statement) defines a function the way we have done so far:
The keyword function
, followed by the function's name, followed by parentheses with optionally the comma separated parameters, followed by
a set of statements, enclosed in curly brackets, which is called the body of the function.
function functionName(param1,param2,etc) {
// body of the function
}
A function is a value, an object. Normally a value is assigned to a variable or constant that was declared using a keyword
var
, let
or const
and an identifier, i.e. a name. This is also possible with function values (see next section about function expressions),
but a function declaration as shown in the example above is a statement on its own that declares the variable and initializes it with a function value.
The function name is the identifier (and needs to be a valid identifier).
You can think of the function declaration as a let
declaration (except at the top level of a script where it behaves more like a var
declaration).
However, reassigning variables initialized by a function declaration is generally not recommended (monkey patching).
// Valid syntax, but avoid doing this:
function myFunc() { console.log("foo"); }
myFunc = "reassignedValue";
// Valid syntax at top level, but avoid doing this:
function myFunc() { console.log("foo"); }
function myFunc(a) { console.log("bar"); }
var myFunc = false;
Like variable and constant declarations, function declarations in JavaScript are hoisted. However, unlike with variables and constants, also the function value is hoisted. More about this in the chapter about function scope.
A function declaration can be a statement directly within global scope (i.e. not within a block statement) or directly
within an other function's body (a nested function). A function declaration within a block statement, like in an if
statement (conditionally created function)
or in a loop results in inconsistent hoisting behavior across user agents when the code is in non-strict mode. In strict mode, (modern) browsers handle it correctly and
let the function only exists within that block scope. However, it is generally safer to use function expressions (see next section) instead of function declarations in
if
statements or in loops. Next example uses function expressions in conditional statements.
let myFunc;
let condition = false;
if (condition) {
myFunc = function () {
console.log("Condition is true");
}
} else {
myFunc = function () {
console.log("Condition is false");
}
}
myFunc(); // logs: Condition is false
Function expressions
A function declaration is syntactically a statement in which the function name is required. When a function definition is an expression that is part of a statement, it is called a function expression. In a function expression a name is optional.
function () {
// function body
};
// logs: SyntaxError: function statement requires a name
// an unnamed function expression as part of a statement:
console.log(
(function () {
return true;
}).toString()
);
// logs:
// "
// function () {
// return true;
// }
// "
Note that both a function declaration and a function expression define (create) a function. The difference is that a function declaration is a statement on its own and a function expression is a fragment of code that needs to be used as a part of a statement.
A function expression is often used in a statement in which it is assigned to a variable or constant, as any object can be assigned to a variable or constant.
const add = function (a,b) {
return a + b;
}
console.log(add(2,3)); // logs: 5
Functions as in the above example are called anonymous functions, since they have no explicit given name. In the example above the function is assigned to a constant with a name, but the function itself is anonymous.
A function with a name can be used in an assignment to a variable or constant. In this case, what on its own could be a function declaration is now used as a function expression within an assignment statement.
const add = function addTwoNumbers(a,b) {
return a + b;
}
const add1 = function (a,b) {
return a + b;
}
console.log(add1.name); // logs: "add1" // implicit name
const add2 = function addTwoNumbers(a,b) {
return a + b;
}
console.log(add2.name); // logs: "addTwoNumbers" // explicit name
console.log(addTwoNumbers); // throws a ReferenceError: addTwoNumbers is not defined
BTW. The above example uses the Function
property
name
which returns the function's name.
Function expressions are not hoisted to the beginning of their scope. The variable or constant is hoisted, but the assigned value, the function in that case, is not (contrary to function declarations). More about this in the chapter about function scope.
Immediately Invoked Function Expression
An Immediately Invoked Function Expression (IIFE, pronounced "iffy"), or Self-Executing Anonymous Function, is an anonymous JavaScript function that executes as soon as the function is declared. An IIFE cannot be called outside the function; it calls itself, once.
(function () {
console.log("Hello World!"); // logs: "Hello World!"
})();
A function can be invoked (called) by adding parentheses ()
right after the function's identifier:
invoking a function "myFunction" is done with the statement myFunction();
.
However, function(){ console.log("Hello World!"); }();
does not self-invoke, but throws an error.
The JS engine first encounters keyword function
and therefore expects a function declaration that requires a name.
Moreover, a function declaration is a
statement and parentheses are only interpreted as a function invoker when they are tailing an
expression.
In the above example the function expression is placed in parentheses. Now the JS engine expects an expression, because
parentheses cannot contain statements. There are other (a tiny little shorter) ways to make the JS engine expect an expression, like placing a negation (!
)
or unary plus operator (+
) before the function
keyword,
but enclosing the function expression in parentheses is most common and makes the code better readable (and does not change the default return value of the function).
An IIFE creates a lexical scope for its own variables, constants and functions. This way it avoids hoisting variables, constants and functions to global scope and it prevents accessing local IIFE variables from elsewhere in the code. At the same time the IIFE maintains access to public variables, constants and functions.
An application may include scripts that do things that are not (directly) related to each other. One script may run an image carousel on a webpage while another script plays a canvas animation somewhere else on the page. To keep both scripts separated, you can place them both in their own IIFE. Variables and functions specific for one script cannot accidentally be interfered with (like redeclared or reassigned a value) by code from elsewhere in the application. Of course you can also try to use unique variable, constant and function names throughout the application, but this requires a lot of accounting and potentially long names. It even becomes more complex if a script is used in multiple applications, like a JS library. There are more ways to tackle this problem available in JavaScript than the use of IIFEs. Later more about this.
let a = 2, b = 3;
(function () {
let number = a + b
console.log(number); // logs: 5
})();
(function () {
let number = a * b
console.log(number); // logs: 6
})();
Arrow function expressions
An arrow function expression is a function expression using a shorthand syntax. However, arrow functions are not in all situations interchangeable with ordinary function expressions. Arrow functions have some limitations and differences compared to ordinary function expressions.
// Both functions do the same:
const sum = function(a, b) {
return a + b;
}
const sumArrow = (a, b) => {
return a + b;
}
Arrow functions are always anonymous functions: you cannot give them an explicit name.
If (and only if) the return statement is the only statement in the function body, you can omit both the return
keyword and the curly brackets.
Returning an object literal requires parentheses around the literal to indicate that the curly brackets belonging to the object literal do not signify a block statement.
const sum1 = (a, b) => {
return a + b;
}
// or:
const sum2 = (a, b) => {
a + b;
}
// or:
const sum3 = (a, b) => a + b;
const sum = (a, b) => ({ sum: a + b });
console.log(sum(2,3)); // logs: { sum: 5 } // returned an object
If (and only if) the function has precisely one parameter, you can omit the parentheses around the parameter, provided that the parameter is not a rest parameter and not a parameter with a set default value (see next chapter).
// arrow function with one parameter:
const square = a => a * a;
// arrow function with no parameters:
const message = () => "3 squared is "+ square(3);
console.log(message()); // logs: "3 squared is 9"
By the way: Instead of () => {};
(an arrow function without parameters) programmers sometimes write
_ => {};
. This function actually has a parameter, the underscore _
, but this parameter is not used.
It is just one character shorter code than using two parentheses ()
.
Rest parameters and default parameters (see next chapter) always require parentheses.
const subtractTwo = (a = 2) => a - 2;
const summate = (...terms) => {
let sum = 0;
for (const term of terms) {
sum += term;
}
return sum;
}
console.log(subtractTwo()); // logs: 0
console.log(summate(2, 4, 6)); // logs: 12
An arrow function can be made self-invoking:
(() => console.log("Hello World!"))(); // logs: Hello World!
As mentioned, arrow functions have some limitations and differences compared to ordinary function expressions.
Unlike ordinary function expressions, arrow functions are always anonymous functions,
arrow functions do not have their own this
(which makes them unsuitable for methods) and
arrow functions cannot be used as constructors.
Later this tutorial these features will be explained in more detail.