Functions in JavaScript
Introduction
A JavaScript function is conceptually similar to a function in Mathematics. At a basic level, a function takes input and uses it to produce output.
/* Defining a function */
const addTwoNumbers = function(x, y) {
return x + y
}
/* Calling a function */
addTwoNumbers(2, 3) //yields 5
Parts of a function
A function has three basic components:
-
The function name, which uniquely identifies the function so that you can call it (use it). In the example above, this is
addTwoNumbers
. -
The function's parameters, which are the input to the function. In the example above, this is
(x, y)
. -
The function's return value, which determines what pops out the other end of the function after you call it. In the example above, this is
return x + y
.
When calling a function, you want to supply it with input values according to its defined parameters. These are called arguments.
In the above example, we call the function addTwoNumbers(x, y)
and pass into it the values 2
and
3
. These arguments map directly onto the parameters in our function definition:
x
gets the value 2
y
gets the value 3
Nullary functions
Sometimes, a function doesn't have any parameters, and therefore doesn't take any arguments:
/* A function without parameters */
const gimmeFive = function() {
return 5
}
/* Calling the function */
gimmeFive() //yields 5
Void functions
Sometimes, a function has no explicit return value, and instead manipulates some data outside of itself:
/*
* This variable lives outside of any function,
* and its value can be manipulated by any function
*/
let counter = 0
/* Add 1 to the counter variable above */
const incrementCounter = function() {
counter = counter++
}
console.log(counter) // prints "0"
incrementCounter()
console.log(counter) // prints "1"
incrementCounter()
console.log(counter) // prints "2"
In the above example, every time we call incrementCounter()
, the value of counter
increases by 1.
Classes in JavaScript: Object Orientation
Introduction
To understand what a class in JavaScript is, we must first understand Object Orientation.
Object Orientation is a programming paradigm — a philosophy of code organization.
The basic unit of object-oriented code is the object.
What is an object?
Objects in programming are just like objects in the real world. Imagine your apartment: It is filled with objects — tables, chairs, rugs, beds, lamps, silverware, and so on.
Let's take one of those types of objects, a chair, and use it to illustrate the principles of Object Orientation.
Object Classes: You know a chair when you see one
You have more than one chair in your home. There are billions of chairs in the world, in many different styles: there are barstools, thrones, armchairs, and so on. And yet, when you look at a chair, you know it is a chair.
To put it philosophically, there is a platonic ideal of a chair, a theoretical definition of what a chair is, that allows us to recognize every chair we see as being a chair. What Platonism calls an ideal, programmers call a class.
As programmers, we would say that all chairs in the world are objects of class Chair. Every barstool is an object of class Chair. A La-Z-Boy is also an object of class Chair.
You might come across an alternative phrasing of this same idea: Every chair in the world is an instance of the class Chair. For our purposes in this guide, these two phrasings mean exactly the same thing.
Object Properties: Not every chair is identical
While all the chairs in the world are objects of the Chair class, there is still a lot of variation in what a chair can look like.
We might say that each chair has a set of properties, or physical traits that govern the way it looks and how you use it. Some of these properties are:
- Number of legs
- Height
- Material
- Color
You may also consider whether or not a chair has a back, whether or not a chair has arms, and so on.
Defining a Chair class in JavaScript
We've described what a chair is in English — now let's do it in code.
/* Defining the Chair class */
class Chair {
constructor(numberOfLegs, height, material, color, hasBack, hasArms) {
this.numberOfLegs = numberOfLegs
this.material = material
this.color = color
this.hasBack = hasBack
this.hasArms = hasArms
}
}
/* Creating an instance of the Chair class */
const barstool = new Chair(3, "metal", "black", false, false)
/* Creating another instance of the Chair class */
const throne = new Chair(4, "wood", "gold", true, true)
Class constructors
Our Chair class has properties that we want to be able to set when we create a new Chair object. By setting these properties, we can customize any Chair object we create.
In the above code, inside the class definition, you see the keyword constructor
.
The constructor is a special kind of function. It takes parameters, just like an ordinary function. The secret sauce is in what it then does with those parameters.
This
Let's take a closer look at the constructor inside our class definition.
class Chair {
/* This constructor takes 6 arguments */
constructor(numberOfLegs, height, material, color, hasBack, hasArms) {
/* A Number, indicating the number of legs */
this.numberOfLegs = numberOfLegs
/* A String, indicating the material the chair is made of */
this.material = material
/* A String, indicating the color of the chair */
this.color = color
/* A Boolean, indicating whether the chair has a back */
this.hasBack = hasBack
/* A Boolean, indicating whether the chair has arms */
this.hasArms = hasArms
}
}
Inside a class definition, the keyword this
refers to an instance of the class itself.
When we create an instance of the Chair class, the constructor gets automatically called. It takes the arguments we've supplied and assigns them directly to our object itself using dot notation. In other words, the constructor builds our object for us, using the values that get passed into it.
Accessing object properties
We use dot notation to access the properties of an object. We've seen how we can use dot notation to assign values to properties, but we can also use dot notation to access the properties of an instantiated object.
/*
* Creating an object, barstool, which is a new instance
* of the Chair class and giving it some properties
*/
const barstool = new Chair(3, "metal", "black", false, false)
/* Asking our object what its properties are */
console.log(barstool.numberOfLegs) // prints "3"
console.log(barstool.color) // prints "black"
console.log(barstool.hasBack) // prints "false"
We can also reset the properties of an object after we've created it:
const barstool = new Chair(3, "metal", "black", false, false)
console.log(barstool.material) // prints "metal"
barstool.material = "platinum"
console.log(barstool.material) // prints "platinum"
We can also access properties from inside the class definition, using the this
keyword, as we'll see in a bit.
But first, there's one last component of JavaScript classes we need to cover — methods.
Class methods
We've seen how functions can take values and mutate them, even changing values that exist outside of themselves.
We can easily leverage the power of a function inside a class and its instantiated objects. A function that lives inside a class definition is called a method.
Method definitions
class Zoo {
constructor(animals) {
/* An array of animals */
this.animals = animals
}
/* Return the number of array elements in this.animals */
getNumberOfAnimals() {
return this.animals.length
}
}
In the above code, we've created a new class called Zoo
. The constructor of the Zoo
class takes a parameter called animals
. This will be an array of strings, representing a list of animals that a Zoo
object has.
Inside the class, we've written a method, getNumberOfAnimals()
, which returns the length of the this.animals
array.
As you can see, the syntax of methods is almost identical to the syntax of functions:
/* Method definition syntax, used inside classes */
myMethod() {
return 'Hello, method!'
}
/* Function definition syntax, used outside of classes */
const myFunction = function() {
return 'Hello, function!'
}
Note: in modern JavaScript, you can actually write your functions with the same syntax you use to write methods, but we're going to put that idea aside for now. We'll return to it in chapter 3.
Calling an object's methods
Now that we've defined a method, let's see how to use it.
class Zoo {
constructor(animals) {
this.animals = animals
}
getNumberOfAnimals() {
return this.animals.length
}
}
/* Create a new Zoo object and give it 3 animals */
const bronxZoo = new Zoo(["lion", "crocodile", "rabbit"])
/* Call the method we've defined in our class */
bronxZoo.getNumberOfAnimals() // yields 3
Notice that we're using dot notation to call our getNumberOfAnimals()
method. This is because that method belongs to the bronxZoo
object. In general, anytime we want to access anything inside of an object, be it a property or a method, we will use dot notation to do it.
/* Accessing an object's property */
bronxZoo.animals // yields ["lion", "crocodile", "rabbit"]
/* Accessing an objects method */
bronxZoo.getNumberOfAnimals() //yields 3
Notice that methods calls have parentheses after them, just like function calls do. If our method took arguments, we would put them inside those parentheses, just like with a function.
Putting it all together
Let's expand our Zoo
class from the previous section.
class Zoo {
constructor(animals) {
this.animals = animals
}
/*******************************
* Method Definitions *
*******************************/
getNumberOfAnimals() {
return this.animals.length
}
addAnAnimal(animal) {
if (typeof animal == "string") {
this.animals.push(animal)
} else {
console.log(animal + " is not a string!")
}
}
removeAnAnimal(animal) {
const notTheSameAnimal = function (string) {
return string != animal
}
this.animals = this.animals.filter(notTheSameAnimal)
}
}
Notice that we've added two more methods, addAnAnimal()
and removeAnAnimal()
. Let's go through them one-at-a-time.
addAnAnimal()
addAnAnimal(animal) {
if (typeof animal == "string") {
this.animals.push(animal)
} else {
console.log(animal + " is not a string!")
}
}
The addAnAnimal()
method adds an animal to a Zoo. It expects one argument, a string, which will be the name of the name of the animal we want to add.
But what if the argument that gets passed in to addAnAnimal()
is not a string? What if it's a number, or a boolean, or an array?
Some programming languages will throw a fit if you get your types mixed up, but JavaScript does its best to handle the issue and keep running, sometimes with unexpected consequences. Therefore, it is good practice to be vigilant about type safety.
if (typeof animal == "string") {
this.animals.push(animal)
}
Inside addAnAnimal()
, we use an if
statement to check the type of the argument, and only insert it into this.animals
if it is a string.
The typeof
operator tells us the type of any variable or value that comes after it. An operator is a special type of function that doesn't use parentheses. There are only a handful of these in JavaScript.
The push()
method belongs to JavaScript's Array
class. Every array you create is a automatically member of the Array
class, which provides you with lots of different handy methods you can use on any array.
else {
console.log(animal + " is not a string!")
}
The else
block is pretty simple — if the argument we've passed in to addAnAnimal()
isn't a string, print a message that says so.
Let's test it out!
const bronxZoo = new Zoo(["lion", "crocodile", "rabbit"])
/* Passing in a valid value */
bronxZoo.addAnAnimal("monkey")
/* Verifying that it works */
console.log(bronxZoo.animals) // prints "[ "lion", "crocodile", "rabbit", "monkey" ]"
/* Trying some invalid values */
bronxZoo.addAnAnimal(47) // prints "47 is not a string!"
bronxZoo.addAnAnimal(false) // prints "false is not a string!"
bronxZoo.addAnAnimal(["shark", "tortoise", "tapir"]) // prints "shark,tortoise,tapir is not a string!"
removeAnAnimal()
removeAnAnimal(animal) {
const notTheSameAnimal = function (string) {
return string != animal
}
this.animals = this.animals.filter(notTheSameAnimal)
}
This method is a little trickier than the last one. Let's break it up.
const notTheSameAnimal = function (string) {
return string != animal
}
The first thing we do is create a function, notTheSameAnimal()
.
Yes, you can create functions inside the body of a method! Same goes for creating functions inside functions. We call these nested functions or inner functions.
So our nested function, notTheSameAnimal()
, takes a string, and returns the result of comparing that string with the animal
string that was passed in to our outer method.
return string != animal
is just a shorthand way of saying:
if(string != animal) {
return true
} else {
return false
}
string != animal
is what we call a boolean expression. At its heart is the not-equals operator, !=
that you're familiar with. It works just like an arithmetic operator — returning the result of an operation on two values. Just like 1 + 2
returns an integer, string != animal
returns either true
or false
.
Since !=
is already returning either true
or false
, wrapping it in an if
statement, while perfectly acceptable, is extraneous code.
Now, let's take a look at how we're using this inner function we've made:
this.animals = this.animals.filter(notTheSameAnimal)
We start off with an assignment, so you know that this.animals
is being updated with the result of the method on the other side of the =
.
Remember how we said that Array.push()
was a built-in method of the Array
class? Array.filter()
is another one of those built-in methods.
The interesting thing about Array.filter()
is what it takes as an argument: another function.
A function (or method) that takes another function as an argument is called a higher-order function. It's just like function composition in Mathematics!
Here's a simple example of how Array.filter()
works:
const isEven = function(value) {
/* The percent sign is the modulo operator, returning the remainder after division */
if(value % 2 == 0) {
return true
}
else return false
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr = arr.filter(isEven)
console.log(arr) //prints "[2, 4, 6, 8]"
You can see that Array.filter()
filters an array according to the result of a boolean function on each element of that array. If the boolean function returns true
, that element stays. If the boolean function returns false
, that element gets thrown away.
Going back to the Array.filter()
method in notTheSameAnimal()
:
this.animals.filter(notTheSameAnimal)
This returns the result of applying our boolean function notTheSameAnimal()
on every member of the this.animals
array. It compares every element of this.animals
with the element we want to remove. Any element that doesn't match stays, and any element that does match gets thrown away.
One important thing to note is that Array.filter()
does not modify the original array. Instead, it produces a new array that contains only the elements that passed the test.
Other Ways to Declare Functions
So far, we've seen one way to declare functions in JavaScript, but there are other ways to do it.
Function expressions vs function statements
This how we've declared functions until now:
const incrementNumber = function(number) {
return number++
}
This is called a function expression.
But we can also do it this way:
function incrementNumber(number) {
return number++
}
This is called a function statement.
Notice that the function statement looks an awful lot like how we declare classes.
There's one big difference between these two types of function declarations which is important to be aware of: a function defined with a function statement can be used anywhere on the page.
This is fine:
function incrementNumber(number) {
return ++number
}
incrementNumber(5) // yields 6
And this is also fine:
incrementNumber(5) // yields 6
function incrementNumber(number) {
return ++number
}
A function defined by a function expression, however, cannot be called before it is declared.
This is fine:
const incrementNumber = function(number) {
return ++number
}
incrementNumber(5) // yields 6
But this will not work:
incrementNumber(5) // throws an error
const incrementNumber = function(number) {
return ++number
}
It's entirely up to you which syntax you choose. There are benefits and drawbacks to both, which you will learn in time.
Functions as arguments
In chapter 2, we saw how functions can be passed in to other function and methods.
removeAnAnimal(animal) {
const notTheSameAnimal = function (string) {
return string != animal
}
this.animals = this.animals.filter(notTheSameAnimal)
}
But we can also create functions on the fly without giving them names. These are called anonymous functions, and JavaScript developers use them all the time.
Array.map()
Let's introduce another built-in method of the Array
class, Array.map()
Like Array.filter()
, Array.map()
takes a function and calls that function on every member of an array.
let arr = [1, 2, 3, 4, 5]
const timesTwo = function(number) {
return number * 2
}
arr = arr.map(timesTwo)
console.log(arr) // prints "[2, 4, 6, 8, 10]"
But while Array.filter()
is used for making a smaller array out of a larger one, Array.map()
returns an array of the exact same size as the original, with every array element changed according to the function you've passed in.
In the above example, we use Array.map()
to call timesTwo()
on every element of arr
, storing the returned array back into arr
.
But we don't need to declare the timesTwo()
function before we pass it in. In fact, we can create the function in the moment we pass it in to to Array.map()
, without needing to give it a name at all.
Anonymous functions
An anonymous function (sometimes also called a lambda function) is a function without a name, which is created at the moment that it is called.
Anonymous functions are great for when you need to pass a function as an argument in to another function to accomplish some small task.
There are two ways to define anonymous functions: the older, more verbose way (which is hardly ever used today), and the newer, more concise way (which JavaScript developers use all the time).
Traditional anonymous functions
Let's return to our Array.map()
example, except this time using an anonymous function instead of a declared one.
let arr = [1, 2, 3, 4, 5]
arr = arr.map(function(number) {
return number * 2
})
console.log(arr) // prints "[2, 4, 6, 8, 10]"
You can see here that instead of declaring a function, giving it a name, and then passing it by its name in to Array.map()
, we are passing the definition of the function itself in as the argument.
Before this, we were creating a function and storing it in a variable, which we called timesTwo
. The value of timesTwo
was the function we assigned to it.
Let's use a simple example to help understand what's going on here.
const a = 5
incrementNumber(a) // yields 6
incrementNumber(5) // yields 6
When you assign a value to a variable, that variable evaluates to whatever you've assigned to it.
In this case, our variable a
evaluates to 5. In other words, a
is equivalent to 5
.
Therefore, passing in a
is the same thing as passing in 5
.
JavaScript variables can hold simple values, like numbers or strings, but as we know, they can also hold functions. In JavaScript, functions are values, just like numbers or strings!
const b = function(args) {
...
}
In the above example, the variable b
evaluates to the function we've assigned to it, in the exact same way a
evaluates to the integer 5
. That is to say, b
is effectively the same thing as the function it represents.
Therefore, when you pass in a function as an argument to some other function, you don't need to store it in a variable first. That variable would simply evaluate to that function anyway.
Arrow functions
In 2015, a new way to create an anonymous function was added to the JavaScript standard: the arrow function.
let arr = [1, 2, 3, 4, 5]
arr = arr.map((el) => el * 2)
console.log(arr) // prints "[2, 4, 6, 8, 10]"
As you can see, the syntax of arrow functions is much more compact. This code, however, does the exact same thing as our previous two Array.map()
examples.
Let's look at the arrow function itself:
(el) => el * 2
This arrow function takes one argument, el
, and has a return value, which is the result of evaluating el * 2
.
Notice that we don't need to use the function
or return
keywords here. JavaScript knows that whatever comes before the =>
is the set of parameters, and whatever comes after the =>
is the return value.
The syntax is as follows:
(parameters) => return value
This is the syntax we use when the thing we want the function do to is very simple, such as multiplying a number by 2.
But, as we know, many functions are much more complicated. What if we want to perform a whole series of operations on the function's arguments?
Multiline arrow functions
When you need a bit more out of your arrow function than a simple one-line operation, the syntax changes slightly.
We can keep the left-hand side of the arrow function exactly as it is, but we need to bring back the curly braces and the return
keyword to the right-hand side.
let arr = [858, 29, 84, 1, 422, 6, 93, 620, 2, 134]
arr4 = arr4.map((num) => {
let numberOfDigits = num.toString().length
if (numberOfDigits == 3) {
return num / 2
} else if (numberOfDigits == 2) {
return num * 3
} else if (numberOfDigits == 1) {
return num
}
})
console.log(arr) // prints "[429, 87, 252, 1, 211, 6, 279, 310, 2, 67]"
In the above code, we're using Array.map()
to go through every number in an array of integers, arr
, and perform the following:
If the element has three digits, divide it by two.
If the number has two digits, multiply it by three.
If the number has one digit, return it just as it is.
The easiest way to get the number of digits in an integer in JavaScript is to convert that integer to a string (using the built-in method of the Number
class, Number.toString()
) and then get the length of that string. That's what we're doing in the first line of our arrow function, storing the result in the variable numberOfDigits
.
As you can see, in a multiline arrow function, the left-hand side of the =>
stays exactly the same, while on the right-hand side, we use the same braces and return
keyword that we would use in any other function.
Glossary
Anonymous function - A function that is declared without being given a name, usually to be used once and immediately. Also called a lambda function.
Argument - A value that is passed in to a function or method, whose usage is described by its respective parameter in the function or method definition.
Arrow Function - A JavaScript anonymous function of the syntax (args) => return value
, introduced to JavaScript in 2015.
Assignment - The process by which a variable is given a value by use of the =
operator.
Class - A template for creating objects which specifies their properties and methods.
Constructor - A special method inside a class that assigns values to an object's properties and does any other necessary work when an object of that class is instantiated.
Data type - The category of a piece of data. There are several data types built in to JavaScript, the most important of which you can read about here.
Declaration - The definition of a variable, function or object, or any other piece of data.
Dot notation - The syntax for accessing the properties and methods of a class or object.
Expression - Anything that evaluates to a value. This can be a literal, such as "hello"
or 3.14
, a function call, or either of these stored in a variable.
Function - A defined procedure which converts data from one form into another.
Function call - The actual using of a function (or method), be it predefined or anonymous.
Higher-order function - A function or method that takes a function as an argument.
Initialization - The assignment of a piece of data to a variable.
Inner function - A function that is created inside another function or method. Also called a nested function.
Method - A function that belongs to a class.
Nullary function - A function that takes no parameters. The number of parameters required by a function is called its arity.
Object - A data structure which is a collection of properties and methods for interacting with those properties.
Object-oriented programming - A programming paradigm (philosophy) based around encapsulating data and code within objects.
Operator - One of a handful of special JavaScript-defined functions that don't use parentheses, such as +
, ++
, and typeof
Outer function - A function or method that contains an inner function.
Parameter - A variable in a function or method definition that stands in for the values to be passed in when that function or method is called.
Property - An attribute of an object, as defined in that object's class.
Type safety - The way a programming language prevents type errors, such as what would occur if you try to pass a number in to a function that takes a string. JavaScript is very permissive when it comes to type safety, so it's up to the programmer to enforce proper typing.
Void function - A function that has no return value. Void functions almost always modify data external to themselves, which is considered to be an implicit return value.