Functions in JavaScript
In this tutorial, we’ll discover various ways to declare functions. Additionally, we’ll see how functions are a type of object, meaning that we can pass them in to and out of other functions.
Learning Outcome Guide
At the end of this tutorial, you should be able to:
- Create functions using Function Declaration syntax
- Create functions using Function Expressions
- Use a function by calling it
- Distinguish between parameters and arguments
- Explain the difference between creating a function and calling a function
- Describe the effects of “function hoisting”
- Supply functions as callbacks to other functions
- Build and return a function from within a function
For the setup of this tutorial, launch VS Code side-by-side with an external terminal. Create a JavaScript file called exploring-functions.js
and run it in the terminal (node --watch exploring-functions.js
).
Function Declarations vs. Function Expressions
-
In an earlier tutorial we discovered that we can create our own functions. Let’s make another function. Enter the following in your script file.
exploring-functions.js function displayHeading(text) {console.log(text);console.log(''.padStart(text.length, '='));}This is an example of using the function declaration syntax for creating a function. Our
displayHeading
is also referred to as a named function, because the name appears as part of the function declaration (that is, between thefunction
keyword and the parameter list).At this point, nothing appears in our terminal’s display. This is because declaring a function merely creates the function. To get a function to run, we must invoke it.
-
Let’s make use of the function by displaying our introductory heading for this tutorial.
exploring-functions.js displayHeading('Exploring Functions in JavaScript');As noted in that previous tutorial, this statement is an example of a function call. The name of the function is followed by a set of parenthesis which contains the data you are sending into the function.
displayheading
is the function name and the literal text'Exploring Functions in JavaScript'
is the argument supplied to that function. The technical term for running or calling a function is called invoking the function.You should now see the following output.
Terminal window Exploring Functions in JavaScript================================= -
Function declarations are a handy way of creating a re-usable set of instructions. There’s something else interesting about functions. Make the following adjustments to your code and see what happens.
exploring-functions.js displayHeading('Exploring Functions in JavaScript');function displayHeading(text) {console.log(text);console.log(''.padStart(text.length, '='));}displayHeading('Exploring Functions in JavaScript');When you create functions using the function declaration syntax, those functions can be called even before they are declared. This is referred to as “declaration hoisting”.
-
Function declarations offer a certain kind of flexibility and clarity to our code in that we can choose to put all our function declarations at the end of our script. This allows the executable portion of our code to be at the top, making it easier for us as developers to focus on a “high-level” view of what our code does. If we want to see the details of our function implementations, we can scroll down the page. In the early days of JavaScript, organizing our code in this fashion made it easier for developers reading our code to get the big picture of what the code does without getting bogged down in the nitty-gritty details.
This approach to organizing our code comes at a cost, however. This cost is one of vulnerability, because any named item (variables or functions) can have their contents overridden at any point. Try adding the following two lines of code.
exploring-functions.js displayHeading('Exploring Functions in JavaScript');displayHeading = 'Where did it go?';displayHeading('Why does this crash?');function displayHeading(text) {console.log(text);console.log(''.padStart(text.length, '='));}Because we reassigned
displayHeading
to hold a string value, it no longer holds the function implementation. We’ve effectively “lost” the function, and trying to call it as a function causes our program to crash.Terminal window Exploring Functions in JavaScript=================================C:\GH\LearningJavaScript\exploring-functions.js:4displayHeading('Why does this crash?');^TypeError: displayHeading is not a functionGo ahead and remove those lines of code.
exploring-functions.js displayHeading = 'Where did it go?';displayHeading('Why does this crash?'); -
Fortunately, there’s a way to protect our function from being lost. We can use a constant along with a function expression instead of a function declaration. We’ll have to give up the benefit of hoisting, however. Re-write your code as follows.
exploring-functions.js // Now, our function must appear before any attempt to call itconst displayHeading = function (text) {console.log(text);console.log(''.padStart(text.length, '='));}displayHeading('Exploring Functions in JavaScript');The
const
keyword is used in place of thelet
keyword in defining our variable. In the example above,displayHeading
is now a variable whose value is “fixed” to whatever appears after the assignment operator. This means thatdisplayHeading
will always be a function, and any attempt to turn it into something else will fail.displayHeading = 'Can I change this?'Terminal window Exploring Functions in JavaScript=================================D:\GH\dgilleland\WIP\0-99\fast-paced\exploring-functions.js:8displayHeading = 'Can I change this?';^TypeError: Assignment to constant variable. -
Before we conclude this part of the tutorial, let’s improve our
displayHeading
function to make it more flexible. Let’s add a parameter for the underline character, so that it’s not restricted to only using the=
character for the underline.exploring-functions.js // Final version of displayHeading() functionconst displayHeading = function (text, underline) {console.log(text);console.log("".padStart(text.length, underline));};displayHeading("Exploring Functions in JavaScript", '=');This last change demonstrates another important aspect of functions. We can have as many parameters as we want in our functions.
It might not seem like it, but with this short example of functions we’ve opened up unlimited windows of opportunity. The humble function in JavaScript is probably the most important contributor to creating large scale applications. This will become even more evident as we explore functions as objects.
Functions as Objects
Whether you use function declarations or function expressions, every single function in JavaScript is an object. This has huge implications, as you shall see.
-
Let’s revisit the
roundToDecimals()
function we created when we were learning basic math in JavaScript. We’ll write it using a function expression this time. Add this code to your script file.exploring-functions.js const roundToDecimals = function (num, decimals) {let factor = Math.pow(10, decimals);let result = Math.round(num * factor) / factor;return result;}This function also serves as a reminder that functions can have many parameters, but can only return at most one single item.
-
What will we see if we use the
typeof
operator with the function name? Try throwing in the following line.exploring-functions.js console.log(`roundToDecimals is a '${typeof roundToDecimals}'.`);As you can see in the output,
roundToDecimals
is a'function'
. A function is a specific type of object, but an object nonetheless.Terminal window roundToDecimals is a 'function'.In JavaScript, functions are first-class objects. That means we can assign a function declaration to an object (as we saw with function expression syntax earlier). Now try adding this line.
exploring-functions.js console.log('Here is the code:\n', roundToDecimals.toString());The terminal shows the actual code of the function. We used the identfier
roundToDecimals
like it was a variable name. Calling.toString()
on any variable returns its value as text. -
Rounding errors can occur with both addition and multiplication. Let’s create a couple of small functions for each operation.
exploring-functions.js const addition = function(first, second) {return first + second;}const multiplication = function(first, second) {return first * second;}So far, so good. Now, let’s explore how we can use these in a demonstration of rounding errors.
-
Create the following function called
demoRounding()
.exploring-functions.js const demoRounding = function(firstValue, secondValue, operation, precision) {let title = `\nThis is a demo of rounding errors`;displayHeading(title, '-');console.log('JavaScript is susceptible to rounding errors. For example:');let result = operation(firstValue, secondValue);console.log(`Combining ${firstValue} with ${secondValue} produced ${result}`);let correctResult = roundToDecimals(result, precision);console.log(`The actual result should be ${correctResult}.`);}Notice the third parameter:
operation
. OurdemoRounding()
function is expecting this variable to contain a function for its value. Specifically, we’re anticipatingoperation
will be a function that accepts two arguments and returns a value. In other words,operation
is a callback function.Let’s call our function by passing in the
addition
function along with the numbers0.1
and0.2
.exploring-functions.js demoRounding(0.1, 0.2, addition, 1);It’s important to see that we are supplying
addition
as an argument when callingdemoRounding
. The addition function will run when it is invoked from inside ofdemoRounding()
.Here’s the result of executing our demo.
Terminal window This is a demo of rounding errors---------------------------------JavaScript is susceptible to rounding errors. For example:Combining 0.1 with 0.2 produced 0.30000000000000004The actual result should be 0.3. -
Let’s try that again. This time we will supply
multiplication
as the callback foroperation
. Add the following line to your existing code.exploring-functions.js demoRounding(0.1, 0.2, multiplication, 2);Here’s the rounding error output.
Terminal window This is a demo of rounding errors---------------------------------JavaScript is susceptible to rounding errors. For example:Combining 0.1 with 0.2 produced 0.020000000000000004The actual result should be 0.02. -
We can make a slight improvement to our
demoRounding()
by indicating what operation we are performing. We can do this by discovering the actual name of our callback function. Every function has a property called.name
that holds the name of the function. (JavaScript needs to know this in order to keep track of where to find that function when it’s invoked.)exploring-functions.js const demoRounding = function(firstValue, secondValue, operation, precision) {let title = `\nThis is a demo of rounding errors with ${operation.name}`;displayHeading(title, '-');console.log('JavaScript is susceptible to rounding errors. For example:');let result = operation(firstValue, secondValue);console.log(`Combining ${firstValue} with ${secondValue} produced ${result}`);let correctResult = roundToDecimals(result, precision);console.log(`The actual result should be ${correctResult}.`);}Now we’ll have an output that is far more descriptive.
Terminal window Exploring Functions in JavaScript=================================This is a demo of rounding errors with addition------------------------------------------------JavaScript is susceptible to rounding errors. For example:Combining 0.1 with 0.2 produced 0.30000000000000004The actual result should be 0.3.This is a demo of rounding errors with multiplication------------------------------------------------------JavaScript is susceptible to rounding errors. For example:Combining 0.1 with 0.2 produced 0.020000000000000004The actual result should be 0.02. -
In the prior steps, we passed
addition
andmultiplication
as arguments todemoRounding
. We can do that because functions are a kind of object. Because of that, a function can also return another function.Let’s create a function that can output a line of text with a number to prefix the text. In other words, we want to turn this:
Terminal window JavaScript is susceptible to rounding errors. For example:Combining 0.1 with 0.2 produced 0.30000000000000004The actual result should be 0.3.into this:
Terminal window JavaScript is susceptible to rounding errors. For example:1) Combining 0.1 with 0.2 produced 0.300000000000000042) The actual result should be 0.3. -
Add the following to your current script. This
buildStepperFunction()
will return a function, as highlighted.exploring-functions.js const buildStepperFunction = function() {let currentStep = 1;return function(text) {let output = `${currentStep}) ${text}`;currentStep++; // Increment by 1console.log(output);}} -
Next, we’ll modify
demoRounding()
to make use ofbuildStepperFunction()
and it’s returned function.exploring-functions.js const demoRounding = function(firstValue, secondValue, operation, precision) {const displayStep = buildStepperFunction();let title = `\nThis is a demo of rounding errors with ${operation.name}`;displayHeading(title, '-');console.log('JavaScript is susceptible to rounding errors. For example:');let result = operation(firstValue, secondValue);console.log(`Combining ${firstValue} with ${secondValue} produced ${result}`);displayStep(`Combining ${firstValue} with ${secondValue} produced ${result}`);let correctResult = roundToDecimals(result, precision);console.log(`The actual result should be ${correctResult}.`);displayStep(`The actual result should be ${correctResult}.`);}We now have an output where each demonstration of rounding errors uses its own numbered steps.
Terminal window Exploring Functions in JavaScript=================================This is a demo of rounding errors with addition------------------------------------------------JavaScript is susceptible to rounding errors. For example:1) Combining 0.1 with 0.2 produced 0.300000000000000042) The actual result should be 0.3.This is a demo of rounding errors with multiplication------------------------------------------------------JavaScript is susceptible to rounding errors. For example:1) Combining 0.1 with 0.2 produced 0.0200000000000000042) The actual result should be 0.02.buildStepperFunction()
returns an object. That object happens to be a function.
Conclusion
A computer program is a set of instructions for manipulating information. Computer programs have inputs and outputs. JavaScript functions are like miniature programs. They have inputs (captured by parameters) and they have outputs (by using return
to send back information).
JavaScript functions are powerful because of these reasons:
- They allow us to modularize our code, making it easier to re-use instructions by invoking a function (like we did with
demoRounding()
androundToDecimals()
). - They can be used as objects, allowing us to pass them into other functions (as callback functions) and return them from functions (as shown with
buildStepperFunction()
).
Even with this ability to modularize code with functions, our programs are getting bigger and bigger. This tutorial itself is almost 50 lines of code. In the next QuickStart tutorial, we’ll discover how we can separate a single JavaScript file into multiple files. This will help us in managing complexity as our JavaScript programs begin to grow.
A computer program is a set of instructions for manipulating information. — Dan Gilleland