Function

Instead of repeating commands each time, it's easier to encapsulate them in a function once.

Time to read: 7 min

Briefly

A function is a block of various commands. It makes it easy to create order in the program code, eliminating unnecessary repetitions and convoluted parts.

How to Write

The first way is to simply declare the function in the code (in English Function Declaration):

        
          
          function hello(name) {  alert(`Hello ${name} 😊`)}
          function hello(name) {
  alert(`Hello ${name} 😊`)
}

        
        
          
        
      

The second is to create a function expression (Function Expression). This is similar to the first way, but here the function becomes the value of a variable:

        
          
          const hello = function(name) {  alert(`Hello ${name} 😊`)}
          const hello = function(name) {
  alert(`Hello ${name} 😊`)
}

        
        
          
        
      
Open demo in the new window

The ways to write a function from the examples above are not the same (although they look almost identical 🤔). The main difference is that if we used Function Declaration, JavaScript will hoist the function to the top of the current scope. This is called "hoisting".

In practice, this means that we can use it before its declaration. We write — make it work, and then explain how. Magic!

        
          
          hello('Ivan')function hello(name) {  alert(`Hello ${name} 😊`)}
          hello('Ivan')

function hello(name) {
  alert(`Hello ${name} 😊`)
}

        
        
          
        
      

Using a Function Expression will cause an error:

        
          
          hello('Ivan')const hello = function (name) {  alert(`Hello ${name} 😊`)}// hello is not a function
          hello('Ivan')

const hello = function (name) {
  alert(`Hello ${name} 😊`)
}

// hello is not a function

        
        
          
        
      

How to Understand

A function declaration can be understood as follows:

  • At the beginning is the keyword function, to declare our intention to declare a function;
  • Then the name of the function, to distinguish one function from another (we have the concise hello, but sometimes there’s nothing concise at all...);
  • In parentheses, we specify the parameters (which can be omitted) that we will pass inside;
  • Finally, the body of the function — this is the code in curly braces, which is executed when it is called.

Calling a function is even simpler. Let's create a new one and name it makeShawarma:

        
          
          function makeShawarma(meat) {  alert(`Your shawarma with ${meat} is ready 🌯`)}
          function makeShawarma(meat) {
  alert(`Your shawarma with ${meat} is ready 🌯`)
}

        
        
          
        
      

To call it, we first write the function name, and then in parentheses specify the argument (or arguments), for example, the word with chicken. We declare: run makeShawarma with chicken inside.

        
          
          makeShawarma('with chicken')
          makeShawarma('with chicken')

        
        
          
        
      
Open demo in the new window

Function Name

A function should be named in a way that its title explains its action. This makes it easier for others to read the code, and you won’t have to remember or figure out what mysterious function IgorMishaPasha123321() means 🤔. The same rule applies to variables: we pass the name — we name it name.

In JavaScript, there are two types of functions based on their name. In the example below, the function is called named because it has a name.

        
          
          function namedFunction() {}
          function namedFunction() {}

        
        
          
        
      

The opposite of named functions is anonymous ones. They have no name:

        
          
          function() {}
          function() {}

        
        
          
        
      

They work the same, but behave differently in the console and call stack. Suppose we wrote a program that has an error. If our functions were named, the call stack will show which function called which and what led to the error:

        
          
          function functionA() {  function functionB() {    throw new Error('Oops!')  }  functionB()}functionA()// Error: Oops!//    at functionB (/index.js:3:11)//    at functionA (/index.js:6:3)
          function functionA() {
  function functionB() {
    throw new Error('Oops!')
  }

  functionB()
}

functionA()

// Error: Oops!
//    at functionB (/index.js:3:11)
//    at functionA (/index.js:6:3)

        
        
          
        
      

Here we can see which functions called which and what led to the error, down to the line number and character. With anonymous functions, it is harder, because there will only be line numbers instead of function names.

Parameters

When calling a function, you can pass data, which will be used by the code inside.

For example, the function showMessage takes two parameters named user and message, and then combines them for a complete message.

        
          
          function showMessage(user, message) {  console.log(user + ': ' + message)}
          function showMessage(user, message) {
  console.log(user + ': ' + message)
}

        
        
          
        
      

When calling the function, it needs to be given arguments. The function can be called as many times as desired with any arguments:

        
          
          showMessage('Masha', 'Hello!')// Masha: Hello!showMessage('Ivan', 'How are you?')// Ivan: How are you?
          showMessage('Masha', 'Hello!')
// Masha: Hello!

showMessage('Ivan', 'How are you?')
// Ivan: How are you?

        
        
          
        
      

Function and Variables

Variables inside a function exist only within that function — this effect is called scope.

        
          
          function five() {  const numberFive = 5}console.log(numberFive)//numberFive is not defined
          function five() {
  const numberFive = 5
}

console.log(numberFive)
//numberFive is not defined

        
        
          
        
      

If you attempt to call them from outside, it will cause an error. In the example above, we will see that numberFive is not defined since we did not actually declare numberFive outside the function.

At the same time, global variables can be used both outside and inside the function:

        
          
          const numberFour = 4function five() {  const numberFive = numberFour + 1  return numberFive}console.log(numberFour)// 4console.log(five())// 5console.log(numberFive)// numberFive is not defined
          const numberFour = 4

function five() {
  const numberFive = numberFour + 1
  return numberFive
}

console.log(numberFour)
// 4
console.log(five())
// 5
console.log(numberFive)
// numberFive is not defined

        
        
          
        
      

Calling the global variable numberFour does not lead to an error, whereas the variable numberFive still exists only within the function.

💡 In the example above, there was the keyword "return". What this is and why it's necessary is discussed in detail in a separate article about return 😎

Arrow Functions

An arrow function is written much more succinctly than a regular one. In the simplest form, the keyword function and curly braces are not required.

        
          
          const divider = (number) => number / 2
          const divider = (number) => number / 2

        
        
          
        
      

In multi-line arrow functions, there is more code, so they have curly braces, but otherwise they do not differ:

        
          
          const divider = (numerator, denominator) => {  const result = numerator / denominator  return result}
          const divider = (numerator, denominator) => {
  const result = numerator / denominator
  return result
}

        
        
          
        
      

Also, arrow functions do not have their own execution context, but more on that below.

Recursive Functions

It is possible to call the function within itself — this is an example of a recursive function.

        
          
          function fac(n) {  if (n < 2) {    return 1  } else {    return n * fac(n - 1)  }}console.log(fac(3))// 6
          function fac(n) {
  if (n < 2) {
    return 1
  } else {
    return n * fac(n - 1)
  }
}

console.log(fac(3))
// 6

        
        
          
        
      

If we break down the example, we get the following chain:

  • fac(3) is 3 * fac(2);
  • fac(2) is 2 * fac(1);
  • fac(1) is 1.

It turns out that fac(3) is 3 * 2 * 1, which equals 6. This approach is often used in mathematical operations but is not limited to them.

Function Context

The code at the time of execution has an "environment". This is the function that is currently executing, the variables contained within it, and the global variables. All this is the context.

🐌 Context is a complex but very important topic, which is why we wrote a separate article about it.

In practice

Advice 1

🛠 When writing a function, parameters are specified — those variables that the function works with. But there are cases when not all parameters are set. This may be done either intentionally, for example, to use a default option, or happen accidentally — an error in usage or unexpected input data.

Open demo in the new window

🛠 Let's give functions names to make debugging easier.

An anonymous function will be harder to debug because its name will not be in the call stack.

        
          
          someElement.addEventListener('click', function () {  throw new Error('Error when clicked!')})
          someElement.addEventListener('click', function () {
  throw new Error('Error when clicked!')
})

        
        
          
        
      

Unlike a named function:

        
          
          someElement.addEventListener('click', function someElementClickHandler() {  throw new Error('Error when clicked!')})
          someElement.addEventListener('click', function someElementClickHandler() {
  throw new Error('Error when clicked!')
})

        
        
          
        
      

🛠 Arrow functions can use implicit return:

        
          
          const arrowFunc1 = () => {  return 42}const arrowFunc2 = () => 42arrowFunc1() === arrowFunc2()// true// Both functions return 42
          const arrowFunc1 = () => {
  return 42
}

const arrowFunc2 = () => 42

arrowFunc1() === arrowFunc2()
// true
// Both functions return 42

        
        
          
        
      

You can also return any structures and data types:

        
          
          const arrowFunc3 = () => 'string'const arrowFunc4 = () => ['array', 'of', 'strings']
          const arrowFunc3 = () => 'string'
const arrowFunc4 = () => ['array', 'of', 'strings']

        
        
          
        
      

To return an object, it must be wrapped in parentheses. Only then will JavaScript understand that we are not opening the function body but returning a result:

        
          
          const arrowFunc5 = () => ({ cat: 'Bars' })console.log(arrowFunc5())// { cat: 'Bars' }
          const arrowFunc5 = () => ({ cat: 'Bars' })

console.log(arrowFunc5())
// { cat: 'Bars' }

        
        
          
        
      

Advice 2

🛠 Anonymous functions are conveniently used in-place, for example, passing them to some method:

        
          
          [1, 2, 3, 4, 5].map(function (num) {  return num * 2})
          [1, 2, 3, 4, 5].map(function (num) {
  return num * 2
})

        
        
          
        
      

Or in the call of another function:

        
          
          function makeCouple(recipe) {  const green = '🍏'  const red = '🍎'  return recipe(green, red)}const result = makeCouple(function(one, two) { return one + two })console.log(result)// 🍏🍎
          function makeCouple(recipe) {
  const green = '🍏'
  const red = '🍎'
  return recipe(green, red)
}

const result = makeCouple(function(one, two) { return one + two })
console.log(result)
// 🍏🍎

        
        
          
        
      

In the examples above, we do not declare the passed function in advance and do not give it a name. And why, if, in the end, it will only be executed once? It is more practical to declare and use it immediately where needed, which is where anonymous functions fit perfectly.