Briefly
Functions are first-class objects. This means that a function can be used just like other data types: stored in a variable, passed as an argument, and returned from a function.
Technically, a function is a JavaScript object that has an internal method Call
, which adds the ability to invoke a function.
If you want to learn about function syntax, read the article function
.
How to Understand
In many languages, functions are special constructs of the language. They are not a data type, and the set of operations that can be performed on them is limited — you can only declare and invoke them.
In JavaScript, a function is a data type, similar to an object or a string. This means that it can be manipulated just like any other data type — stored in a variable, passed as an argument to a function, returned from functions.
It is convenient to think of a function as an object that supports the call operation.
Storing a Function in a Variable
Functions can be declared in various ways. Declaring a function via a function expression is nothing more than assigning an anonymous function to a variable:
const answer = function() { console.log('42!')}answer()// 42!
const answer = function() { console.log('42!') } answer() // 42!
You can also store a function that is declared in another way in a variable. Both function names will work:
function answerNumber() { console.log('42!')}const answer = answerNumberanswerNumber()// 42!answer()// 42!
function answerNumber() { console.log('42!') } const answer = answerNumber answerNumber() // 42! answer() // 42!
A variable stores a reference to the function, so you can create as many variables as you need, and all of them will reference the same function:
const answer = function() { console.log('42!')}const answerNumber = answerconst fn = answer
const answer = function() { console.log('42!') } const answerNumber = answer const fn = answer
Passing a Function to Call Another Function
A function can be passed as an argument when calling another function.
For example, a function that can perform an arbitrary operation between two numbers. The two numbers are stored within the function, and the operation to be performed is passed when called:
function performOperation(operation) { const a = 10 const b = 99 return operation(a, b)}const sum = performOperation(function(one, two) { return one + two })console.log(sum)// 109const result = performOperation(function(num1, num2) { return num1 ** (num1 / num2)})console.log(result)// 1.2618568830660204
function performOperation(operation) { const a = 10 const b = 99 return operation(a, b) } const sum = performOperation(function(one, two) { return one + two }) console.log(sum) // 109 const result = performOperation(function(num1, num2) { return num1 ** (num1 / num2)}) console.log(result) // 1.2618568830660204
Thus, the logic of the operation can be defined outside the function, making it flexible.
Functions that expect to receive another function as a parameter are commonplace in JavaScript. Even built-in methods, such as for
and filter
use this approach.
Another use case is callbacks in asynchronous code. Sometimes it is necessary to perform an operation after some action has finished. For example, when the user clicks a button. In this case, the method add
is used, which takes the name of the event and the callback to be invoked when it occurs:
document.getElementsByTagName('button')[0].addEventListener('click', function() { console.log('User clicked!')})
document.getElementsByTagName('button')[0].addEventListener('click', function() { console.log('User clicked!') })
Returning a Function as a Result of a Call
A function can be returned as the result of another function. For example, you can save data for a mathematical operation but not execute it immediately; instead, return a function that will perform the operation on specified numbers:
function lazySum(a, b) { return function() { return a + b }}
function lazySum(a, b) { return function() { return a + b } }
Here, it is easy to get confused with the nesting. When calling lazy
, we pass two arguments. These arguments are not used immediately — we create a new function that sums the two numbers and return it. After calling lazy
, we can store this function in a variable and use it when needed:
const performSum = lazySum(99, 1)console.log(performSum)// function lazySum()console.log(performSum())// 100
const performSum = lazySum(99, 1) console.log(performSum) // function lazySum() console.log(performSum()) // 100
Note that the values of parameters a
and b
remain accessible inside the nested function. This feature is related to execution context and lexical environment of functions. This approach is also actively used in JavaScript development.
In practice
Advice 1
🛠 To understand what is stored in a variable, it's enough to use the typeof
operator. For functions, it returns the string 'function'
:
const answer = function() { console.log('42!')}console.log(typeof answer)// 'function'
const answer = function() { console.log('42!') } console.log(typeof answer) // 'function'
🛠 Since a function is technically an object, it has properties and methods. For example, the length
property will return the number of function parameters:
const answer = function() { console.log('42!')}console.log(answer.length)// 0const sum = function(a, b) { return a + b}console.log(sum.length)// 2
const answer = function() { console.log('42!') } console.log(answer.length) // 0 const sum = function(a, b) { return a + b } console.log(sum.length) // 2
🛠 Properties can be added to functions like to regular objects. This kind of code is rare, but don’t be surprised if you see:
const calc = function() {}calc.type = 'numbers'console.log(calc.type)// numbers
const calc = function() {} calc.type = 'numbers' console.log(calc.type) // numbers