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} 😊`) }
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 make
:
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 make
with chicken
inside.
makeShawarma('with chicken')
makeShawarma('with chicken')
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
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 show
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 number
is not defined since we did not actually declare number
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 number
does not lead to an error, whereas the variable number
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
is( 3 ) 3 * fac
;( 2 ) fac
is( 2 ) 2 * fac
;( 1 ) fac
is 1.( 1 )
It turns out that fac
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.
🛠 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.