Functions can be thought of as one of the core building blocks of our JavaScript programs. A function is simply a set of statements designed to perform a particular task — which is executed whenever it is invoked (or “called”).

In this article we’re going to take a look at defining functions, function expressions, calling functions, scope, nesting, closures, the arguments object, function parameters & arrow functions!

Defining functions

A function definition (otherwise known as a function declaration, or function statement) will always begin with the functionkeyword. What follows is the name of our function, any parameters enclosed in parenthesis, and then our JavaScript statements (enclosed in { }) which are executed when our function is called.

Let’s see an example:

function multiply(a, b) {
  return a * b;
}

Our function multiply takes two parameters (a and b). Included in-between the curly braces is a statement that returns the result of the first parameter a multiplied by the second parameter b.

Function expressions

There is another way to define a function, known as the function expression. These types of functions can actually be anonymous. They don’t need to be given a name.

For example, our previous multiply function could’ve also been defined like so:

let multiply = function(a, b) { return a * b; };
let x = multiply(2,2); // x returns 4

A typical use case for function expressions might be when passing a function as an argument to another function.

We can also define a function based on a condition. For example, the following function defines addItem only if num equals 1:

let addItem;
if (num === 1) {
  addItem = function(shoppingList) {
    shoppingList.item = 'Apples';
  }
}

Calling functions

When we define a function all we’ve done is given it a name & specified what to do when the function executes. So to actually run the function, we’ll need to call it within our program!

To call our earlier function multiply, we could invoke it as follows:

multiply(2,2);

Here we’re calling our function which is set to receive two arguments with the arguments of 2 and 2. The function executes and runs its statements which returns the result of 4(2 multiplied by 2).

Functions need to be in scope when they are called, but the function declaration can be hoisted (appear below the call in the code), like so:

console.log(multiply(2,2));
/* ... */
function multiply(a,b) { return a * b; }

The scope of a function is the function in which it is declared, or the entire program if it is declared at the global level.

Note: This only works with function declarations. Not function expressions!

Function scope

When variables are defined inside a function they cannot be accessed anywhere outside of that function. As that would be outside the scope. However, a function is able to access all variables and functions defined inside the scope in which it is defined. So a function defined in the global scope can access all variables defined in the global scope. A function defined inside another function can also access all variables defined in its parent function and any other variable to which the parent function has access.

Let’s see an example...

// These variables are defined globally (global scope)
let a = 10,
    b = 3,
    name = 'Bruce';
// This function is defined globally 
function multiply() {
  return a * b;
}
multiply(); // returns 30
// Working with a nested function
function multiplyAssign() {
  let a = 20,
      b = 6;
  
  function add() {
    return name + ‘ received’ + (a * b);
  }
  
  return add();
}
multiplyAssign(); // returns "Bruce received 120"

Nesting functions and closures

So you can nest a function within a function! The nested (inner) function is private to its containing (outer) function. It also forms a closure. A closure is an expression (usually a function) that can have free variables together with an environment that binds those variables (it “closes” the expression).

And as a nested function is a closure, it’s able to “inherit” the arguments and variables of its containing function.

As follows:

function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41

Since the inner function forms a closure, you can call the outer function and specify arguments for both the outer and inner function!

Closures

As we now know, we can nest our functions and JavaScript will give the inner function full access to all the variables and functions defined inside the outer function (as well as all variables & functions that it has access to).

However, the outer function does not have access to the variables and functions defined inside the inner function! A closure is created when the inner function is somehow made available to any scope outside the outer function.

Let’s see an example:

// The outer function defines a "name" variable
let user = function(name) {   
  let getName = function() {
    return name;             
    // The inner function can access the "name" variable of the  outer function
  }
// Return the inner function, and expose it to outer scopes
return getName;            
}
makeAdmin = user('Steve Stevesson');
   
makeAdmin(); // returns "Steve Stevesson"

The arguments object

The arguments of any function are maintained in an array-like object. Within a function, you can address the arguments passed to it as follows:

arguments[i]

Here i is the first number of the argument, starting at zero. So, the first argument passed to a function would be arguments[0]. The total number of arguments would be arguments.length.

Using the arguments object, you can call a function with more arguments than it is formally declared to accept. This is often useful if you don't know in advance how many arguments will be passed to the function. You can use arguments.length to determine the number of arguments actually passed to the function, and then access each argument using the arguments object.

For example, consider a function that concatenates several strings. The only formal argument for the function is a string that specifies the characters that separate the items to concatenate. The function is defined as follows:

function myConcat(separator) {
   let result = ''; // initialize list
   let i;
   // loop through arguments
   for (i = 1; i < arguments.length; i++) {
      result += arguments[i] + separator;
   }
   return result;
}

You can pass any number of arguments into this function, and it’ll concatenate each argument into a string “list”:

myConcat(', ', 'fred', 'wilma', 'barney', 'betty');
// returns "fred, wilma, barney, betty, "
myConcat('; ', 'marge', 'homer', 'lisa', 'bart', 'maggie');
// returns "marge; homer; lisa; bart; maggie; "
myConcat('. ', 'jerry', 'kramer', 'elaine', 'george', 'newman');
// returns "jerry. kramer. elaine. george. newman. "

Note: The arguments variable is "array-like", but not an array. It has a numbered index and a length property. However, it does not possess all of the array-manipulation methods.

Function parameters

There are two kinds of function parameters: default parameters and rest parameters. Let’s now take a look at each...

Default parameters

In JavaScript, parameters of functions will default to undefined. However, in some situations, it might be useful to set a different default value. This is where default parameters are useful.

It’s actually quite simple to implement:

function multiply(a, b = 1) {
  return a * b;
}

multiply(10); // 10

You can simply put 1 as the default value for b in the function head.

Rest parameters

The rest parameter syntax allows us to represent an indefinite number of arguments as an array.

In this example, we use the rest parameters to collect arguments from the second one to the end. We then multiply them by the first one. This example uses an arrow function, which we’ll look at next!

function multiply(multiplier, ...theArgs) {
  return theArgs.map(x => multiplier * x);
}

let arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

Arrow functions

An arrow function has a much shorter syntax compared to function expressions. For example, here is a regular function:

function funcName(params) {
  return params + 10;
}

funcName(2); // 12

And here is the same function, expressed as an arrow function:

let funcName = (params) => params + 10

funcName(2); // 12

The same function in just one line of code! Pretty neat!

If we have no parameters, we express our arrow function like this:

() => { statements }

And when we have just one parameter, the opening parenthesis are optional:

parameters => { statements }

Finally, if you’re returning an expression, you can remove the brackets:

parameters => expression

// is the same as:

function (parameters){
  return expression;
}

Note: Unlike a regular function, an arrow function does not bind this. Instead, this is bound to keep its meaning from its original context. We’ll be taking a closer look at the “this” keyword in an upcoming article!

Predefined functions

It’s well worth noting that JavaScript has numerous built-in functions! And they’ll likely save you a lot of time, especially for common tasks. See:
https://www.tutorialspoint.com/javascript/javascript_builtin_functions.htm

Wrapping up

And that’s all for today! We’ve learned about defining functions, function expressions, calling functions, scope, nesting, closures, the arguments object, function parameters & arrow functions!

Related Posts: