Step 2: Functions

April 30, 2021

In the previous section, we have learned about the console.log function. A function is a programming construct that combines different operations for purposes like reusability, convenience, etc.

In this section, we will learn more about functions. We will also learn about defining our functions. Functions are incredibly powerful programming structures that make building complex programs possible.

JavaScript and the environments that we use JavaScript in, such as Web Browsers, have many built-in functions that we can use. console.log is an example of such a function.

For the reasons that we will be learning about later, there is a . character in the name of this function.

To use a function, we need to call it (also know as executing a function). If we were to type the name of the function and execute that code, nothing would happen.

console.log;

Calling a function requires us to place parenthesis next to it.

console.log();

When we execute this function, still nothing happens. That is because console.log function requires us to pass an input (or inputs) to it to work correctly. The input to a function is called an argument. We provide the arguments to a function inside the parentheses. Let's call the console.log function with an argument.

console.log("hello");

We already know what console.log function does. It displays the given input to the screen. A function can take one or multiple arguments, depending on how it works. Multiple arguments to a function are separated by the comma (,) character. We can also pass multiple inputs to the console.log function. It would display each input on a separate line.

console.log("hello", "world");

Let's take a look at some other built-in JavaScript functions.

parseFloat Function

parseFloat is a built-in JavaScript function. It works with a single input and tries to convert it into a floating-point number (a fractional number). Here is how it works.

parseFloat("3.14");

This operation would convert the string 3.14 to a numeric 3.14. We can ensure that is the case by making use of that value in operation.

const piNumberAsString = "3.14";

console.log(piNumberAsString + 10);
console.log(parseFloat(piNumberAsString) + 10);

The first operation where we are using the pi number as a string would result in the value 3.1410 because when adding a string and a number, they simply get combined. It doesn't work as a mathematical operation.

But the second operation where we are converting the pi number into a floating-point number would result in 13.14 because it is now a mathematical operation in between two numbers.

This example might have been hard to read because of the nested functions (function calls inside function calls) in the second operation.

const piNumberAsString = "3.14";

console.log(parseFloat(piNumberAsString) + 10);

To make this example easier to read, we can save the parseFloat operation inside a variable and use that variable instead of calling parseFloat inside another function. Here is how to do that:

const piNumberAsString = "3.14";
const piNumber = parseFloat(piNumberAsString);

console.log(piNumberAsString + 10);
console.log(piNumber + 10);

Now, this function would return the same result as before but is more readable. This was possible because the parseFloat function returns the result of its operation (more on returning later).

If we provided a value to the parseFloat function that can't be converted into a floating-point number, it would return a NaN.

const result = parseFloat("hello");
console.log(result);

parseInt Function

parseInt is a built-in JavaScript function that is very similar to parseFloat. It tries to convert the given value to an integer number (also known as whole numbers).

If we provided a value to the parseInt function that can't be converted into an integer number, it would return a NaN.

const piNumberAsString = "3.14";

console.log(parseInt(piNumberAsString)); // Would result in 3

In this example, parseInt function takes a floating-point number and converts it into an integer (3 in this case).

Thinking About Functions

We now know the three most important things to think about when using a function.

  • What a given function does.
  • What kind of inputs does it require (and how many)?
  • What kind of output does it have?

Having answers to these questions helps us be more confident when using a function.

Let's answer these questions for the functions that we have learned about so far.

console.log

  • What does it do? It displays the given inputs to the screen.
  • What kind of inputs does it require? It works with one or more inputs of any kind. The input can be a number or a string. It can be other data types that we haven't learned about as well.
  • What kind of output does it have? It doesn't have an output. We can verify this by trying to capture the output of the function inside a variable.
const result = console.log("hello world");
console.log(result);

We would see that the result is undefined. This means that the result simply does not exist.

parseFloat

  • What does it do? It tries to convert the given input to a floating point number.
  • What kind of inputs does it require? It works with a single input. The input can be a number or a string.
  • What kind of output does it have? The output is a floating-point number if the given value is successfully converted. If not, this function would return a NaN value.

parseInt

  • What does it do? It tries to convert the given input to an integer number (whole number).
  • What kind of inputs does it require? Unlike parseFloat, the parseInt function works with two inputs. The second input is optional, and we can ignore it for our learning purposes.
  • What kind of output does it have? The output is a whole number if the given value is successfully converted. If not, this function would return a NaN value.

We can think of functions as black boxes. It accepts one or more inputs and can have an output. But we don't necessarily know what happens inside. Think of an ATM (Automated Teller Machine). An ATM can take your bank card and your commands as an input, do something with those commands, and can have money and receipts as output. You don't necessarily know what goes on inside an ATM machine either.

Other JavaScript Functions

Here are few other JavaScript functions that are pretty useful.

Math.abs

  • What does it do? Returns an absolute value for a given number. In mathematics, the absolute value of a number is the positive value for that number. It means that if we pass a negative number to this function, it will return the same number but without the negative sign in front of it.
  • What kind of inputs does it require? It works with a single argument that is a number.
  • What kind of output does it have? The output is a positive number of a given kind. It returns NaN if a non-numeric input is provided.
const value = -42;
const absoluteValue = Math.abs(value);
console.log(absoluteValue); // Would be `42`

Math.random

  • What does it do? It returns a "random" (actually pseudo-random) number between 0 and 1 when it is called.
  • What kind of inputs does it require? It doesn't require any inputs.
  • What kind of output does it have? The output is a floating-point number that is between 0 and 1.
const randomNumber = Math.random();
console.log(randomNumber);

Defining Functions

Using built-in functions would only take us so far. We would eventually want to define our own functions to be able to write more advanced programs. Let's assume an operation that we would like to convert into a function, such as displaying Hello World to the screen.

console.log("Hello World");

We want to create a function with the name hello, which would execute this statement when called.

hello();

Here is how to do it. We declare a function by using the function keyword and coming up with a name for our function. In this case, we will call the function hello. We add parentheses next to the name and curly braces after that.

function hello() {}

This is now a function definition for a function named hello. When called, this function wouldn't do anything because the function body, which is in between braces, is currently empty.

hello();

Inside the function definition braces, we should define the operations that we would like this function to perform. In this case, it would be placing the console.log statement in there.

function hello() {
  console.log("Hello World");
}

This function would now display Hello World to the screen when called.

hello();

Functions with Parameters

What if we wanted to create a function that would take user-provided input? For example, think of a function called greet that would display a greetings message customized for the given input. Where running this function below:

greet("John");

would display a message like Welcome John.

To be able to do this, we need to define a function that has parameters.

function greet(firstName) {}

Here we defined a function called greet that accepts a single parameter saved under a variable name firstName.

This means that when we call this function by providing an input (which is also called an argument). It would get that provided input and assign it to a variable called firstName. In more technical words, the argument that we pass to a function gets mapped to the parameter that is in the function definition.

greet("John");

We can verify this by making use of that argument inside our function. Let's start by just using a console.log statement.

function greet(firstName) {
  console.log(firstName);
}

Now, this function would display the given input to the screen, whatever that given input is.

greetings(42);

We want to display a welcome message by using this greetings function. This is how we would do it:

function greet(firstName) {
  console.log("Welcome " + firstName);
}

This function would now display a welcome message customized for any given name.

greet("John");
greet("Jane");
greet("Humans");

The name of the parameter in our example is firstName. We could have chosen any name for this parameter, and the example still would have worked. It is an arbitrary name. The rules and conventions around choosing variable names apply to choosing parameter names and function names as well. We should always choose variable names that make sense.

Additionally, when naming functions, try to use a verb. A function usually describes an action, so it makes sense to name functions using verbs.

Functions with Multiple Parameters

We can also define functions that have multiple parameters. Each parameter that we define should be separated with a comma (,).

function greet(firstName, lastName) {}

Here we defined a function called greet that takes two parameters: firstName and lastName.

Here is what such a function can look like:

function greet(firstName, lastName) {
  console.log("Welcome " + firstName + " " + lastName);
}

This function now makes use of both of these parameters. We can pass two arguments to this function, separated by a comma.

greet("John", "Doe");
greet("Jane", "Doe");

We can define functions that accept as many arguments as we would like.

Functions that Return a Value

Let's create a function that would calculate the final price of a product given the discount amount.

If the product's price is 100$, and it has a 20% discount, then its final price would be 80$. Our function should do this calculation for us.

getFinalPrice(100, 20);

Here is what the definition of that function would look like.

function getFinalPrice(initialPrice, discount) {
  const finalPrice = initialPrice - (initialPrice * discount) / 100;
  console.log(finalPrice);
}

getFinalPrice(100, 20);
getFinalPrice(50, 20);
getFinalPrice(200, 60);

Let's try to use this function to calculate the final price of a 100$ product that had a discount of 20% and then additional 30% over the already discounted price. We might be tempted to do something like this:

function getFinalPrice(initialPrice, discount) {
  const finalPrice = initialPrice - (initialPrice * discount) / 100;
  console.log(finalPrice);
}

const priceAfterFirstDiscount = getFinalPrice(100, 20);
const finalPrice = getFinalPrice(priceAfterFirstDiscount, 30);

This code wouldn't work. getFinalPrice function does a great job at calculating the discounted price of a product, but it just displays the resultant price as a result. It does not return the calculated value from the function. That is why the value of priceAfterFirstDiscount variable after this operation is undefined:

const priceAfterFirstDiscount = getFinalPrice(100, 20);

We need to ensure that this function returns the result of the calculations from inside the function to the outside context, where we call the function to assign that result to a variable. We can do this by using the return keyword.

function getFinalPrice(initialPrice, discount) {
  const finalPrice = initialPrice - (initialPrice * discount) / 100;

  return finalPrice;
}

const priceAfterFirstDiscount = getFinalPrice(100, 20);
const finalPrice = getFinalPrice(priceAfterFirstDiscount, 30);
console.log(finalPrice);

We have removed the console.log statement from inside the function. We are now using a return keyword to return the finalPrice from inside the function to the outside context.

return is a keyword that can only be used inside functions. It returns the value next to it from the function to the context that the function is called in.

Using the return keyword, we can now create a function that can do some calculations and return that value for further processing.

We now know quite a bit about functions. Let's continue our learning by building two useful functions.

A Real World Function Example

We can calculate a bunch of values regarding a circle given its radius.

The diameter of a circle is two times its radius.

The circumference of a circle is calculated using formula 2 * π * r, where π is the pi number (3.14), and the r is the radius of a circle.

The area of a circle is calculated by the formula π * r * r where π is the pi number (3.14), and the r is the radius of a circle.

Let's create a function that would calculate these three values for a given radius value and display them to the screen.

function getCircleData(radius) {
  const piNumber = 3.14;

  const diameter = radius * 2;
  const circumference = diameter * piNumber;
  const area = piNumber * radius * radius;

  console.log("For the circle of radius: " + radius);
  console.log("Diameter is: " + diameter);
  console.log("Circumference: " + circumference);
  console.log("Area is: " + area);
}

Notice that this function doesn't make use of the return keyword. It merely displays the results to the screen. What if we wanted to return these results from the function? If we tried to do so, we might find out that the return keyword only allows us to return one value from a function.

There are data structures in JavaScript we can use to store multiple values and return that one data structure from a function instead. We will learn about those later.

It is usually a better idea to create functions that only do one thing at a time. So this program would be better if it was written like this:

function getDiameter(radius) {
  return radius * 2;
}

function getCircumference(radius) {
  const piNumber = 3.14;
  const diameter = getDiameter(radius);

  return diameter * piNumber;
}

function getArea(radius) {
  const piNumber = 3.14;

  return piNumber * radius * radius;
}

function getCircleData(radius) {
  const diameter = getDiameter(radius);
  const circumference = getCircumference(radius);
  const area = getArea(radius);

  console.log("For the circle of radius: " + radius);
  console.log("Diameter is: " + diameter);
  console.log("Circumference: " + circumference);
  console.log("Area is: " + area);

  return diameter, circumference, area;
}

const result = getCircleData(5);
console.log(result);

Our code turned out to be lengthier in this case, but we have now created a more robust program. It has a bunch of single-purpose functions. Having single-purpose functions make it easier to reason what is happening in our programs. If something is not working, we can just check to see what is happening in the function responsible for the calculation that is not working as expected.

Another benefit of having single-purpose functions is that we now have more primitive building blocks that we can utilize differently later. For example, we now have a function that is only responsible for calculating a circle area. It is potentially more usable in different contexts than a function that calculates the area and does a bunch of other things.

One potential improvement we can make to this function is creating a shared variable for the piNumber. In this example, both the getCircumference and the getArea function defined their own piNumber. This is not only wasteful, but it also breaks the Single Source of Truth principle. It is not like the pi number would change from one function to another, so we shouldn't define it inside each function that uses it. Better to create a variable that we can reuse inside each function

const piNumber = 3.14;

function getDiameter(radius) {
  return radius * 2;
}

function getCircumference(radius) {
  const diameter = getDiameter(radius);

  return diameter * piNumber;
}

function getArea(radius) {
  return piNumber * radius * radius;
}

function getCircleData(radius) {
  const diameter = getDiameter(radius);
  const circumference = getCircumference(radius);
  const area = getArea(radius);

  console.log("For the circle of radius: " + radius);
  console.log("Diameter is: " + diameter);
  console.log("Circumference: " + circumference);
  console.log("Area is: " + area);

  return diameter, circumference, area;
}

const result = getCircleData(5);
console.log(result);

Now, this is much better. There is one more learning to be had here, and that is regarding the scope of variables.

The variable piNumber is accessible from inside the getArea or getCircumference functions. Any variable declared outside the context of a function would be accessible from functions inside that context. This doesn't work the other way around, though. A variable that is defined inside a function wouldn't be accessible from outside that function. For example, the diameter variable declared inside the getCircumference function wouldn't be accessible outside that function. Trying to do so would result in an error.

const piNumber = 3.14;

function getCircumference(radius) {
  const diameter = getDiameter(radius);

  return diameter * piNumber;
}

console.log(diameter);

Callback Functions and Anonymous Functions

A callback function is just a descriptive name for a function that gets passed into another function. The function that receives the callback function chooses when and how it wants to execute the function. Here is a straightforward example of a callback function.

function someOperation() {
  console.log("this is a function that represents some operation");
}

function logStartAndEndForOperation(callbackFunction) {
  console.log("start of operation");
  callbackFunction();
  console.log("end of operation");
}

logStartAndEndForOperation(someOperation);

In this example, someOperation is a callback function in relation to logStartAndEndForOperation function since it is passed as an argument to that function. logStartAndEndForOperation function takes a callback function as an argument and determines how to call that given function.

There is a function called setTimeout in JavaScript. setTimeout takes a callback function and a value that specifies a millisecond amount. setTimeout executes the callback function after the given amount of milliseconds. Here is how it works.

function helloWorld() {
  console.log("Hello World");
}

const oneSecond = 1000; // one second is 1000ms
const threeSeconds = oneSecond * 3;

setTimeout(helloWorld, threeSeconds);

In this example, helloWorld is a callback function. It is passed into setTimeout as an argument, and it gets called by the setTimeout after three seconds.

Callback functions are a frequently used pattern in JavaScript. We will see more usages of callback functions in the following chapters.

Anonymous Functions

A concept that is closely related to callback functions is anonymous functions. An anonymous function is a function that is defined without specifying a name. We usually do this for defining functions that we won't reuse in our program. It saves us from having to come up with a name for a function that is only going to be used once. In the above example, we could define the someOperation function as an anonymous function instead.

function logStartAndEndForOperation(callbackFunction) {
  console.log("start of operation");
  callbackFunction();
  console.log("end of operation");
}

logStartAndEndForOperation(function () {
  console.log("this is a function that represents some operation");
});

We are now defining a function inside the parentheses of the logStartAndEndForOperation function. This function doesn't have any name, so it is an anonymous function. We won't be able to reuse it since we have no way of referring to it. Using anonymous functions as callback functions is a common pattern in JavaScript.

There is one more function concept that is closely related to callback functions and anonymous functions. That is the arrow function expression.

Arrow Function Expression

Arrow function expression is a shorthand way of creating functions. We have previously seen how to create functions using the function keyword.

function myFunction() {}

Arrow function expression allows us to create a function without using the function keyword.

const myFunction = () => {};

This is an arrow function. Arrow functions don't have a name, but we can save them inside a variable to assign a name to them. They really shine when we are working with anonymous functions since they are much shorter to write than function statements. Let's rewrite the above example using arrow functions.

function logStartAndEndForOperation(callbackFunction) {
  console.log("start of operation");
  callbackFunction();
  console.log("end of operation");
}

logStartAndEndForOperation(() => {
  console.log("this is a function that represents some operation");
});

The anonymous function was a bit easier to write using the arrow function. Arrow functions have some different properties than normal functions, but these differences are not very important for us at the moment. We can simply think of them as function expressions that are easier to write.

Summary

In this chapter, we have learned about functions. Some functions are built-in to the JavaScript language (or part of the environment that JavaScript runs), such as parseFloat, parseInt, Math.random, Math.abs, and console.log.

When using functions, three essential things that we need to know about are what do they do, what kind of inputs do they require, and what kind of output do they have.

We have also learned about creating our own functions. We can define functions that have any number of parameters. Parameters of a function are defined inside parentheses and are separated by a comma. We can call functions by providing arguments, and those arguments get mapped to the parameter names that we have defined. We can use the return keyword to return a value from a function.

It is important to build functions that have a single responsibility and use verbs when naming functions since they represent actions.

Finally, three closely related function concepts are callback functions, anonymous functions, and arrow function expression. A callback function is simply a name given to a function passed as an argument to another function. setTimeout is an example of a JavaScript function that works with a callback function.

We can define functions without giving names to them. In which case, they are referred to as anonymous functions. An arrow function expression is a shorter way of defining functions and is suitable for declaring anonymous functions.

Functions are incredibly powerful programming structures that make building complicated applications possible. We will be using them a lot moving forward.

Find me on Social Media