In this lesson, we'll learn all about functions in JavaScript.
- Learn the definition of a function
- Identify the parts of a function
- Differentiate between function defintions and function invocations
Function is a term that comes out of mathematics. You may remember hearing it in your high school algebra class.
The basic idea of a function is simple — it's a relationship between a set of inputs and a set of outputs.
Take a look at the relationship between the variable x
and function named f
in the example below.
Function f
takes in the input of x
and returns a single output. Together, it looks like f(x)
.
Can you guess what this function does to the value of x
?
f(-2) => -4
f(-1) => -2
f(0) => 0
f(1) => 2
f(2) => 4
Assuming that it's the same function, what is the output of:
f(f(3))
In JavaScript, a function can be:
- Made up of either a single reusable statement or even a group of reusable statements.
- Can be called anywhere in the program, which allows for the statements inside a function to not be written over and over again.
Functions are especially useful because they enable a developer to segment large, unwieldy applications into smaller, more manageable pieces. You might hear this described as making the code modular. Modularity is very good and very efficient.
We're going to start off with some basics, such as simple math equations and string interpolation. But every time you Log In to a website, Like a post on Facebook, add an item to your cart on Amazon, or anything else we do across the web, we are working with Functions. But like those CSS properties, you will be amazed at how complex and powerful the functions you write in the coming weeks will be.
Here's an example of what a function can do:
This repeats for every additional movie.
const movie1 = 'Saving Private Ryan'
const year1 = 1998
console.log(`${movie1} was released in ${year1}`)
const movie2 = 'Interstellar'
const year2 = 2014
console.log(`${movie2} was released in ${year2}`)
const movie3 = 'Jason Bourne'
const year3 = 2016
console.log(`${movie3} was released in ${year3}`)
This is all easy enough to write out, but if we have a lot of movies this results in a lot of repeated code! Also, if we want to change the formatting, we have to change it in every place.
Let's try to keep our code from getting out of hand by using a function.
const showMovie = (movie, year) => {
console.log(`${movie} was released in ${year}`)
}
showMovie('Saving Private Ryan', 1998)
showMovie('Interstellar', 2014)
showMovie('Jason Bourne', 2016)
Notice how much cleaner and simpler this function looks than our repeated lines of code?
Using functions is a great way to save time for yourself, and simplify things for your team members.
Functions are a critical component of programming because they allow us to execute on a key tenant of software engineering:
"Don't Repeat Yourself" (aka "DRY" code).
Our goal is to craft our programs in as few lines of code as possible, while still being clear.
-
Performance - Having repeated code will lead to longer scripts. Longer scripts take up more memory and will take more time to load, which can make a website seem slow.
-
Maintainability - Imagine that we have the same line of code repeated 10 times in our program. If we ever want to change that functionality, we would have to search for all 10 places where that code is repeated and make 10 individual changes.
Check out this link to review the three main reasons that functions are created.
Now we know what functions are and why we use them. But how do we create them?
As you saw in our movie example, just as we do with a variable, we must define a function before we call or "use" it.
In JavaScript, functions can be defined in several ways. We will be focusing on using arrow function expressions since that is the modern way of writing them.
Let's take a look at the different parts of the arrow function:
const pickADescriptiveName = () => {
// code block
}
// 'pickADescriptiveName' is the function name
// the parenthesis and arrow are short hand for the declaration of a function . . . also known as an arrow function
// parentheses are needed and can have optional, multiple parameters, or default parameters
Have you ever tried to move forward to the next page of an online form, only to be greeted by an alert that says "Please fill out all the required fields"?
This kind of code can be placed in a function and this function can be called anytime the user hasn't filled out a field on any form on the site. Let's code for this fake popup alert using a function expression:
const errorAlert = () => {
alert('Please be sure to fill out all required fields')
}
Let's take a look at the function in more detail:
- The first line begins by declaring a variable called
errorAlert
. This is the name that we can then use to call that function. - Next, you have a list of parameters surrounded by parentheses. In very specific cases the parenthesis are optional, however, we suggest that you include the parentheses as you are gaining familiarity with functions.
- The statements inside the function will run every time the function is called. The function body must always be wrapped in curly braces
{ }
, even when it consists of only a single statement. NOTE that there is an exception to this rule as well, however, we will still encourage you to always use curly braces as you are getting comfortable with functions.
Now that we've learned about arrow functions, let's discuss naming conventions.
You may have noticed how we capitalize names in JavaScript using the camelCase style.
Let's take a quick look at some good and bad examples of function names, and what can cause them to break down:
-
Ugly: thisfunctioncalculatestheperimeterofarectangle (no camelCase, too verbose)
-
Bad: my new function (contains spaces)
-
Bad: myNewFunction (doesn't explain what it does!)
-
Good: calculateArea (describes what it does, short, and uses camelCase)
To run the code in a function, we call, or invoke, the function by using the function name followed by parentheses.
Remember our function showMovie?
What happens if we just type showMovie
?
Now we add the parentheses.
showMovie()
Parameters are the names listed in the function definition.
Let's write a function that calculates the area of a rectangle.
We need to provide our calculateArea
function with a width and a length so we won't need to create a separate function every time we want to measure the dimensions of a new room.
const calculateArea = (width, length) => {
console.log(width * length)
}
calculateArea(5, 3); // 15
calculateArea(12, 16); // 192
- Width and length are our parameters. Note that they are in a comma separated list. This tells our function that we are going to give it a width and a length.
- There is nothing special about the words
width
andlength
here. These are just descriptive names to remember what information that is being given to our function. We could use other names as well i.e.width
andheight
,l
andw
, etc - When the name of the function is followed by
()
the function is being called. By passing comma separated values in between the parentheses (i.e. arguments) correlate to the name of the parameters when the function was declared. Ex:5
relates towidth
and3
relates tolength
. Note that the order does matter.
To write functions with more than one parameter, use a comma separated list:
e.g., (parameter1, parameter2, parameter3, parameter4, etc.)
Here is an example of a function with four strings as parameters:
const greetUser = (firstName, lastName, year, city) => {
console.log(`Hello ${firstName} ${lastName}, I was born in ${year} and I'm from ${city}.`)
}
Check: What would happen if we called the function with the following arguments?
greetUser('Bruce', 'Wayne', 1939, 'Gotham')
What would happen if we didn't provide all of the parameters?
The code in a function will not run when the function is defined. The code will only run when the function is called.
A Block statement is used to group code together. To create a block, we use a pair of curly braces:
{
// The inside of a multiline block
}
Blocks are also used in functions, conditionals and loops:
if ( /* true || false */ ) { /* within the block, body of conditional */ }
for ( /* let i = 0; ...*/ ) { /* within the block, body of loop */ }
while ( /* i < num ... */ ) { /* within the block, body of loop */ }
function ( /* arg1, arg2 */ ) { /* within the block, body of function */ }
In addition to grouping code together, a block creates a new, localized, scope for the variables defined within the block.
When declaring variables using the ES6 let
and const
initializers inside of a code block, these variables have block scope, meaning the variables are only recognized in the scope of the block they have been created (or declared) in.
You can think of scope as a collection of nested boxes. Each scope acts as a container in which variables and functions can be declared. While JavaScript is executing code within a scope, it only has access to variables declared in the current scope, parent scopes, and the global scope.
Scopes in JavaScript come in two flavors: block scope and function scope. When you create a function, that function has it's own scope. Any variables defined within a function can only be accessed in the scope of that function.
The following are examples of block and function scopes:
{ /* creates block scope */ }
if { /* creates block scope */ }
for ( /* ... */ ) { /* creates block scope */ }
while ( /* ... */ ) { /* creates block scope */ }
function ( /* ... */ ) { /* creates a function scope */ }
The outer most scope of a program is the global scope and all block and function scopes are considered local scopes. Variables are accessible within the scope they are declared:
const name = 'Danny' // this variable is being declared in the "global scope"
{
const name = 'Caleb' // this variable is being declared in a "block scope"
}
console.log(name) // prints 'Danny' because this line is being run in the global scope and NOT the block scope of the program.
// name = 'Caleb' is limited in scope to the block in which it is defined
Variables are also accessible to any inner scopes (child scopes) and can be reassigned locally without modifying the value on a global level.:
// global scope
let x = 1
if (true) {
// local scope
x = 2
console.log(x) // Since x modified in the local scope the output will be 2
}
// global scope
console.log(x) // Since this line is outside of the local scope the output will be 1
However, variables declared in local scopes are not accessible to parent scopes:
// global scope
const x = 1
if (true) {
// local scope
const y = x // we declare a variable of y and assign it the value found in x
console.log(y) // 1
}
// global scope
console.log(x) // 1
console.log(y) // ReferenceError: y is not defined <-- we get this error because y is not declared in the global scope.
Variables are not accessible from sibling scopes:
if (true) {
// local scope of 1st sibling
const a = 1
console.log(a) // 1
}
if (true) {
// local scope of 2nd sibling
console.log(a) // ReferenceError: a is not defined
}
Different scopes can have variables that are declared with the same name and they do not conflict or know about each other.
// global scope
const x = 1
console.log(x) // 1
if (true) {
// local scope
const x = 2
console.log(x) // 2
}
// global scope
console.log(x) // 1
As we have seen, utilizing scope provides great utility. Scoping provides a way to encapsulate data and prevent other parts of our applciation from accessing variables declared within a certain scope. By being aware of how scope is created, and by using scope effectively, you will write code that is more efficient, organized and less error prone.
- Apply the Principle of Least Privilege: Allow code to access the information and resources that are necessary for it to run, and nothing more.
- Encapsulate code as much as possible in scope using functions and blocks
So far, the functions that we have created have simply printed the result of whatever logic we have defined to operate in the function.
However, we might want our functions to return a value back to our program or even exit a function before it runs some logic.
We can accomplish these tasks by using the return
keyword inside of our function definitions.
Sometimes we don't necessarily want to show or log something immediately to the console, or update something on the page.
Instead, we might just want to update a variable within a function, or even call another function without showing its effects.
To do this, we use return
statement.
Let's look at an example of updating a variable within a function.
const doubleValue = (x) => {
return x * 2
}
The return
statement stops the execution of a function and returns a value from that function.
When we return
something, it ends the function's execution and "spits out" whatever we are returning.
We can then store this returned value in another variable.
We can also use return
by itself as a way to stop the function and prevent any code after it from running.
Take a look at this example:
const rockAndRoll = (muted) => {
const song = 'Yellow Submarine'
const artist = 'The Beatles'
if (muted) {
return // Here we use return as a way to exit a function, instead of returning any value
}
console.log(`Now playing: ${song} by ${artist}`)
};
rockAndRoll(true)
Here, we use return
as a way to exit the function instead of returning any value.
So when we call the function passing in true
as an argument for muted
, the log statement will never run.
How can we modify our area function to return our value instead of printing it?
Functions are very important in JavaScript, and you must have a thorough understanding of how to define them, as well as how you can use them.