In a real-world project, you might have pieces of code for different purposes. For instance, you could have a for
loop that processes the elements in an array, another piece of code that asks the user to provide an input, and many more.
If not organized appropriately, this would put a lot of pressure on future maintenance. Not only because it is very easy to lose track, but also because there is no way to reuse your code, you'll have to rewrite it over and over again.
Luckily, there is a fix. Function is a way for you to organize pieces of your program.
How to define functions
JavaScript offers three different ways to define a function: function expression, function declaration, and arrow function. Let's start with the function expression syntax.
Function expression
For instance, here is an example of a function that counts all even numbers in the array:
1let countEven = function (arr) {
2 let n = 0;
3 let e;
4
5 for (e of arr) {
6 if (e % 2 === 0) {
7 n++;
8 }
9 }
10
11 return n;
12};
The keyword function
defines a function in JavaScript, and the parentheses define the function's input arguments. In this case, the function only accepts one input argument, arr
, which can then be used inside the function as a variable.
Look inside the function body, defined by the curly braces ({}
), and notice that the input argument arr
is treated as an array, and we are iterating over the array with the for of
loop.
At the end of the function body, right before the closing }
, the final result (n
) will be returned using the keyword return
, which also marks the end of the function. The function will terminate after the return
statement, so any statements afterward will be ignored.
If you don't have a return
statement in the function, that function will return undefined
instead.
Lastly, the function will be assigned to the variable countEven
, and to invoke that function, simply call by its name and pass the required input arguments.
1let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3console.log(countEven(arr));
The built-in function console.log()
, which we've seen many times in this course, will take the output of countEven()
, and print it to the console.
15
You can simplify this function a little bit by declaring thee
element inside thefor
loop like this:
1let countEven = function (arr) {
2 let n = 0;
3
4 for (let e of arr) {
5 if (e % 2 === 0) {
6 n++;
7 }
8 }
9
10 return n;
11};
This syntax is called function expression. Some people find it difficult to write, compared to the function declaration syntax which we are going to discuss later, but it does demonstrate what a function is.
A function is just a piece of code as a value, which is then assigned to a variable.
Function declaration
There is also a slightly shorter way of creating a function called function declaration, which is demonstrated below:
1function countEven(arr) {
2 let n = 0;
3
4 for (let e of arr) {
5 if (e % 2 === 0) {
6 n++;
7 }
8 }
9
10 return n;
11}
Here we are using the same function
keyword, countEven
is the function name, and everything else remains the same.
This function can be called in the exact same way:
1let arr = [1, 2, 3];
2
3countEven(arr);
Arrow function
Alternatively, there is a special syntax called arrow function in JavaScript, which aims to provide an even shorter way to create functions.
1let countEven = (arr) => {
2 let n = 0;
3
4 for (let i = 0; i < arr.length; i++) {
5 if (arr[i] % 2 === 0) {
6 n++;
7 }
8 }
9
10 return n;
11};
12
13let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
14
15console.log(countEven(a));
The name arrow function comes from the arrow (=>
).
The arrow function can be expressed in a compact format under certain scenarios. For example, the parentheses can be omitted when the function only takes one input parameter.
1let countEven = arr => {
2 . . .
3};
When the function body has only one statement, the curly braces and even the keyword return
can be omitted.
1// prettier-ignore
2let calcSquare = x => x * x;
3
4console.log(calcSquare(5));
125
The differences
These three methods do have some slight differences. First of all, by default, JavaScript executes the code line by line. When using the function expression syntax, you can only call a function after it has been declared.
1let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3console.log(countEven(arr)); // <--
4
5let countEven = function (arr) {
6 let n = 0;
7
8 for (let e of arr) {
9 if (e % 2 === 0) {
10 n++;
11 }
12 }
13
14 return n;
15};
In this example, the function countEven()
is called before it is declared. And this code will return an error.
1/Users/. . ./index.js:3
2console.log(countEven(arr)); // <--
3 ^
4
5ReferenceError: Cannot access 'countEven' before initialization
6 at Object.<anonymous> (/Users/. . ./index.js:3:13)
7 . . .
8
9Node.js v20.12.2
The arrow function works the same. An error will be returned if you try to call an arrow function before it is declared.
1let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3console.log(countEven(arr)); // <--
4
5let countEven = (arr) => {
6 let n = 0;
7
8 for (let e of arr) {
9 if (e % 2 === 0) {
10 n++;
11 }
12 }
13
14 return n;
15};
1/Users/. . ./index.js:3
2console.log(countEven(arr)); // <-- The function countEven() is called before it is declared.
3 ^
4
5ReferenceError: Cannot access 'countEven' before initialization
6 at Object.<anonymous> (/Users/. . ./index.js:3:13)
7 . . .
8
9Node.js v20.12.2
However, the function declaration method is different. JavaScript will compile all the declared functions first, and then start to execute the code from the first line, which means you can call a function before it is declared.
1let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3console.log(countEven(arr)); // <--
4
5function countEven(arr) {
6 let n = 0;
7
8 for (let e of arr) {
9 if (e % 2 === 0) {
10 n++;
11 }
12 }
13
14 return n;
15}
15
This provides you with a more flexible way to organize your functions. For example, you can place all of your functions together at the beginning or the end of the file, without worrying about any conflicts.
Another difference has to do with the this
keyword, which refers to the current instance of the object. Arrow functions don't have a this
keyword like regular functions. We will come back to this topic after we've discussed more about objects.
Function arguments
The functions in our previous examples only accept one input argument, but in fact, JavaScript functions are pretty flexible about the number of arguments you give them. It is possible to define a function that accepts a list of arguments, optional arguments, or even an indefinite number of arguments.
When the function must deal with multiple input arguments, you can separate them with commas. For example, sum3()
is a function that calculates the sum of three numbers.
1let sum3 = function (num1, num2, num3) {
2 return num1 + num2 + num3;
3};
The function declaration syntax and the arrow function work similarly.
1function sum3(num1, num2, num3) {
2 return num1 + num2 + num3;
3}
1let sum3 = (num1, num2, num3) => num1 + num2 + num3;
This function requires three arguments, and when when you call the function with less than three arguments, the ones left out will be assigned undefined
.
1function sum3(num1, num2, num3) {
2 console.log(`num1 is ${num1}`);
3 console.log(`num2 is ${num2}`);
4 console.log(`num3 is ${num3}`);
5 return num1 + num2 + num3;
6}
7
8console.log(sum3(1, 2));
1num1 is 1
2num2 is 2
3num3 is undefined
4NaN
If you pass too many arguments, the extra ones will simply be ignored.
This flexibility can be useful in practice, as it enables you to create functions that accept any number of arguments, but it does lead to problems sometimes.
For instance, in our previous example, JavaScript cannot calculate 1 + 2 + undefined
, so it returns NaN
, not a number.
To prevent this from happening, it is best to give the argument a default value.
1function sum3(num1, num2, num3 = 0) {
2 console.log(`num1 is ${num1}`);
3 console.log(`num2 is ${num2}`);
4 console.log(`num3 is ${num3}`);
5 return num1 + num2 + num3;
6}
1console.log(sum3(1, 2));
This way, instead of undefined
, num3
will be assigned 0 if a value is not provided when the function is called.
Functions as values
At the beginning of this lesson, we defined a function as a piece of code as a value, which is assigned to a variable.
This definition is in fact, a very important concept in JavaScript, and we have to talk more about it. Understanding this concept allows you to write code at a higher level.
- Return a function
We know from the past examples that the return
statement can return a variable, so naturally, it can return a function as well.
1function a() {
2 console.log("Function a executed");
3
4 return function b() {
5 console.log("Function b executed");
6 };
7}
You can call the function a()
like this:
1a();
1Function a executed
Executing function a()
will return a function, and you can assign this returned function to a variable.
1let x = a();
x
will become a variable with a function attached to it, and you can execute the function like this:
1let x = a();
2
3x();
1Function a executed
2Function b executed
- Accept a function as an input argument
A function can accept variables as the input argument, so naturally, it can accept functions as well.
1function decide(status, yes, no) {
2 if (status) {
3 yes(); // If status is true, execute this function
4 } else {
5 no(); // If status is false, execute this function
6 }
7}
In this case, arguments yes
and no
are expected to be functions. If status
is true
, function yes()
will be executed. If status
is false
, function no()
will be executed.
Next, let's create functions a()
and b()
.
1function a() {
2 console.log("Function a executed");
3}
4
5function b() {
6 console.log("Function b executed");
7}
8
9decide(true, a, b);
10decide(false, a, b);
1Function a executed
2Function b executed
In this case, function a()
is passed to decide()
as the argument yes
, and function b()
is passed as the argument no
.
- Functions defined inside functions
You can define variables inside a function, so naturally, you can define functions as well.
1function out(status) {
2 function a() {
3 console.log("Function a executed");
4 }
5
6 function b() {
7 console.log("Function b executed");
8 }
9
10 if (status) {
11 a();
12 } else {
13 b();
14 }
15}
Inside the function out()
, there are two other functions, a()
and b()
. The out()
function takes one input argument, status
. If status
is true
, function a()
will be executed, and if status
is false
, function b()
will be executed.
1out(true);
2out(false);
1Function a executed
2Function b executed
- Copy the function to another variable
You can copy a value to another variable like this:
1let a = 10;
2
3let b = a;
4let x = b;
5
6console.log(a);
7console.log(b);
8console.log(x);
110
210
310
So, a function can also be copied to another variable, and you can call that function with the new name.
1function a() {
2 console.log("Function executed");
3}
4
5let b = a;
6let x = b;
7
8a();
9b();
10x();
1Function executed
2Function executed
3Function executed
These functions that work on other functions are called higher-order functions. They are very useful in practice, and we will talk about them in detail.
At first, they can be difficult to understand, so to prepare yourself for the challenges that lie ahead, please take some time to truly understand this definition:
A function is just a piece of code as a value, which is assigned to a variable.
Lastly, a function can be placed inside an object as a property. In this case, the function will be called a method.
1let obj = {
2 name: "John Doe",
3
4 myFunction: function () {
5 console.log("Function executed");
6 },
7};
8
9obj.myFunction();
1Function executed
Why functions?
Take a look at this example:
1// Calculate the area of rectangle #1
2let length1 = 10;
3let width1 = 8;
4
5area1 = length1 * width1;
6
7// Calculate the area of rectangle #2
8let length2 = 15;
9let width2 = 3;
10
11area2 = length2 * width2;
12
13// Compare the area of two rectangles
14if (area1 < area2) {
15 console.log("Rectangle #1 is smaller than rectangle #2");
16} else if (area1 == area2) {
17 console.log("Rectangle #1 and rectangle #2 have equal area");
18} else {
19 console.log("Rectangle #1 is bigger than rectangle #2");
20}
This example calculates the area of two rectangles and then compare them, but notice that the first part (from line 1 to line 5) and the second part (from line 7 to line 11) are very similar. If we have more rectangles to compare, we'll just be repeating the same code over and over again.
In practice, you should always try to avoid that, because having more code means more space for potential mistakes, and makes your code difficult to read and understand. This would be an excellent chance to turn that code block into a function.
1function area(length, width) {
2 return length * width;
3}
4
5area1 = area(10, 8);
6area2 = area(15, 3);
7
8// Compare the area of two rectangles
9if (area1 < area2) {
10 console.log("Rectangle #1 is smaller than rectangle #2");
11} else if (area1 == area2) {
12 console.log("Rectangle #1 and rectangle #2 have equal area");
13} else {
14 console.log("Rectangle #1 is bigger than rectangle #2");
15}
And now your code is shorter and much easier to read.
Another thing we need to mention is the function name. When declaring a function, you should pick a name that is short but also descriptive. Here are some examples:
1function a() {. . .}
2
3function f() {. . .}
4
5function abc() {. . .}
These functions are short, but they don't adequately describe their purpose. For a real-life project, you will likely have to create dozens or even hundreds of functions. It is impossible to keep track of them if they are all named like f()
or abc()
.
On the other hand, the following examples are descriptive, but are too long to be practical.
1function calculateTheAreaOfASquare() {. . .}
2
3function decideWhichChoiceIsBetter() {. . .}
In the best-case scenario, your functions should fit inside the program naturally like this:
1function isEven(number) {
2 return number % 2 === 0;
3}
4
5let num1 = 8;
6
7if (isEven(num1)) {
8 console.log(num1 + " is even");
9} else {
10 console.log(num1 + " is odd");
11}
In the field of programming, this is usually referred to as abstraction. As you know, the test for parity is one of the more cryptic expressions we have seen so far in this course. It is not immediately clear what the expression is supposed to do.
But in this case, we are hiding the expression behind the word isEven
, allowing us to write the program at a higher and more abstract level.
This is also the second sign telling you that you need a function. When you are writing a program, and you encounter a feature that cannot be easily coded, and it feels like it requires a function.
For example, you might be building a feature that needs the user to be verified, and you need to test the user's status before taking the next step. In this case, it might be a good idea to put this feature behind a function.
1function userVerified(user) {
2 . . .
3}
4
5if (userVerified(user)) {
6 . . .
7} else {
8 . . .
9}
Now, whenever you need to verify a user, simply call the function userVerified()
, which makes your code much easier to read.