Day 2: Advanced Functions and Loops
Review of Functions and Loops (10 minutes)
Welcome back! Today, we dive deeper into the heart of JavaScript as we explore Advanced Functions and Loops. To best grasp these concepts, let’s start by taking a brief moment to review what we’ve learned on Day 1.
Brief Recap of Day 1 Concepts
Functions
In JavaScript, functions are one of the fundamental building blocks. A function is a JavaScript procedure—a set of statements that performs a task or calculates a value. To use a function, you must define it somewhere in the scope from which you wish to call it.
A function definition (also called a function declaration, or function statement) consists of:
- The function keyword,
- The name of the function,
- A list of parameters to the function, enclosed in parentheses and separated by commas,
- The JavaScript statements that define the function, enclosed in curly brackets, {…}.
For example:
function square(number) {
return number * number;
}
In this example, square is the name of the function, number is a parameter, and the function squares the input number.
Loops
Loops offer a quick and easy way to do something repeatedly. There are many different kinds of loops, but they all essentially do the same thing: repeat an action multiple times.
The various loop mechanisms offer different ways to determine the start and end points of the loop. There are various types of loops including: while, do while, for and for in.
Here is a basic example of a for loop:
for (let i = 0; i < 5; i++) {
console.log(i);
}
This loop will log the numbers 0 through 4 to the console.
Address Any Questions or Concerns from Day 1 Exercises
We have set up an automated system to help you revisit and revise any difficulties you encountered with Day 1 exercises. We understand that some concepts might be challenging. Don’t worry! Struggling with new concepts is a part of the learning process.
If you had any specific questions or concerns from the exercises in the Day 1 module, we recommend you revisit the section in the material and check the solutions provided. We have added detailed explanations for the solutions to help you understand where you went wrong. If the problem persists, please do not hesitate to reach out to our support team, who are ready to assist you.
Now that we’ve done a recap of Day 1’s concepts, let’s proceed with today’s exciting content—Advanced Functions and Loops. Keep your JavaScript console open and be ready to write and run some code!
Higher-Order Functions (45 minutes)
JavaScript is known as a “functional” programming language, which can be a bit of a misnomer as it can also handle object-oriented and procedural paradigms. However, it shines when using functions as first-class citizens. This concept is most evident when exploring higher-order functions.
Introduction to Higher-Order Functions
Definition and Purpose
Higher-order functions are a significant aspect of JavaScript and form the basis of functional programming in the language. A function is referred to as higher-order if it fulfills one or both of the following conditions:
- The function accepts one or more functions as arguments.
- The function returns a function as a result.
Higher-order functions allow us to create more abstract and modular code, thereby improving the maintainability and readability of our programs.
Examples of Higher-Order Functions in JavaScript
JavaScript’s Array object is packed with methods that are higher-order functions. Here are a few examples:
forEach()map()filter()reduce()
Each of these methods accepts a function as an argument and performs different operations.
Exploring Common Higher-Order Functions
forEach()
The forEach() method is a simple way to iterate over an array’s items. This method takes a function as an argument and applies it to each element in the array.
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((number) => console.log(number));
In the example above, the forEach() method logs every number of the array to the console.
map()
The map() method is used when you want to transform elements in your array. map() constructs a new array by applying a function to all elements in an array.
const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map((number) => number * number);
console.log(squares);
In this example, map() takes each number of the array, squares it, and then adds the squared result to a new array, squares.
filter()
The filter() method is used when you want to select certain elements from your array. filter() constructs a new array with all elements that pass the test implemented by the provided function.
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
console.log(evenNumbers);
In this example, filter() examines each number of the array, and if it’s even, it adds the number to a new array, evenNumbers.
Practical Exercises on Higher-Order Functions
Implementing forEach() to Iterate Over an Array
Let’s write some code! Using the forEach() method, let’s print the double of each number in the array to the console.
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((number) => console.log(number * 2));
Using map() to Transform Array Elements
Now, let’s use the map() method to create a new array that contains the squares of each number in the original array.
const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map((number) => number * number);
console.log(squares); // Output: [1, 4, 9, 16,
25]
Applying filter() to Select Specific Elements from an Array
Finally, let’s apply the filter() method to create a new array that contains only the odd numbers from the original array.
const numbers = [1, 2, 3, 4, 5];
const oddNumbers = numbers.filter((number) => number % 2 !== 0);
console.log(oddNumbers); // Output: [1, 3, 5]
By now, you should have a solid understanding of higher-order functions and how they work. Remember, practice makes perfect. Feel free to experiment with these functions until you feel comfortable with them. These are powerful tools in your JavaScript arsenal, so be sure to understand them well!
Recursion (45 minutes)
Recursion, a concept that often stumps new programmers, is a method where the solution to a problem depends on solutions to smaller instances of the same problem. This can be a very powerful tool in writing algorithms.
Understanding Recursion
Definition and Characteristics
Recursion in programming is a process in which a function calls itself as a subroutine. This allows the function to be repeated several times, as it can call itself during its execution.
This approach can be very useful for tasks such as sorting, or traversing the nodes of complex or non-linear data structures.
One of the essential characteristics of a recursive function, or algorithm, is the existence of a base case— a terminating scenario that doesn’t use recursion to produce an answer. This scenario must be reached to avoid infinite recursion.
Recursive vs. Iterative Approaches
There’s often a trade-off between recursion and iteration. While recursive functions are generally more straightforward and easier to understand than their iterative counterparts, they also use more memory and have a risk of causing a stack overflow error if the depth of the recursion is too large.
An iterative approach, on the other hand, might be more efficient in terms of memory usage as it doesn’t require the additional memory of the call stack.
Recursive Function Implementation
Recursive Function Structure
The structure of a recursive function can be broken down into two parts:
- Base case: The condition under which the function stops calling itself. This prevents infinite loops.
- Recursive case: The part of the function that calls itself, breaking down the larger problem into smaller sub-problems.
Base Case and Recursive Case
For a recursive function to be useful, it must progress towards its base case. The base case is the condition under which the recursion ends. Essentially, the base case tells the recursive function when to stop.
Each recursive call should make progress towards reaching the base case. Most commonly, this involves reducing a certain parameter.
Recursive Examples and Exercises
Calculating Factorial Using Recursion
The factorial of a non-negative integer n is the product of all positive integers less than or equal to n. It’s a perfect case for a recursive solution:
function factorial(n){
if(n === 0 || n === 1){
return 1;
}else{
return n * factorial(n - 1);
}
}
console.log(factorial(5)); // Output: 120
In this example, n * factorial(n - 1) is the recursive case (making progress towards the base case), and if(n === 0 || n === 1) is the base case.
Implementing Fibonacci Sequence With Recursion
The Fibonacci sequence is a series of numbers in which each number is the sum of the two preceding ones. Here’s how it can be implemented with recursion:
function fibonacci(n){
if(n === 1 || n === 2){
return 1;
}else{
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
console.log(fibonacci(6)); // Output: 8
In this example, the base case is if(n === 1 || n === 2), and the recursive case is fibonacci(n - 1) + fibonacci(n - 2).
These exercises should give you a clear understanding of recursion. Try to write more recursive functions as practice, such as calculating the power of a number, or finding the greatest common divisor (GCD) of two numbers.
Advanced Loop Techniques (45 minutes)
While we have covered the basics of loops, there are more advanced techniques that can enhance your ability to manipulate and traverse data. These techniques are especially useful when dealing with larger and more complex data structures.
Loop Control Statements
Loop control statements change the execution from its normal sequence. JavaScript supports the following control statements: break and continue.
Break Statement
The break statement, which was briefly introduced in the context of switch statements, is used to exit the current loop and continue execution at the following statement, just after the loop. The impact of break is not limited to switch statements, but can be used with any looping statement, like for, while, or do...while.
Here is an example:
for (let i = 0; i < 10; i++) {
if (i === 5) {
break;
}
console.log(i);
}
In the above example, the loop will stop when i is equal to 5, and thus, console.log(i) will only print the numbers 0 to 4.
Continue Statement
The continue statement is used to skip the rest of the code inside the current iteration in the loop and continue with the next iteration of the loop.
Here is an example:
for (let i = 0; i < 10; i++) {
if (i === 5) {
continue;
}
console.log(i);
}
In the above example, when i equals 5, the loop will skip the current iteration, meaning console.log(i) won’t execute, and the loop will then continue with the next iteration. So, console.log(i) will print the numbers 0 to 4 and 6 to 9.
Iterating Over Complex Data Structures
Nested Loops
Nested loops are loops that exist inside the body of another loop. There might be a situation when a block of code needs to be executed several numbers of times. In such a case, nested loops are used.
Here’s an example of a nested loop:
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
console.log(`i: ${i}, j: ${j}`);
}
}
In this example, for each iteration of the outer loop (i loop), the inner loop (j loop) runs 5 times, logging the current values of i and j to the console. This results in 25 total iterations of the inner loop.
Looping Over Multidimensional Arrays
Nested loops are particularly useful when you need to access elements inside a multidimensional array or a nested object.
Here’s an example of how to access elements in a 2D array:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
console.log(matrix[i][j]);
}
}
In this example, we use a nested loop to access and log each element inside the 2D array.
Advanced Loop Exercises
Using break to Exit a Loop Early
Use the break statement to exit the loop as soon as you
encounter a number greater than 10.
let numbers = [3, 7, 9, 14, 16, 20];
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] > 10) {
break;
}
console.log(numbers[i]);
}
Applying continue to Skip Specific Iterations
Use the continue statement to skip even numbers and only print odd numbers.
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue;
}
console.log(i);
}
Nested Loop Scenarios and Exercises
Use nested loops to print a simple 2D pattern.
for (let i = 0; i < 5; i++) {
let row = '';
for (let j = 0; j < 5; j++) {
row += '* ';
}
console.log(row);
}
This will print a 5×5 pattern of asterisks.
Keep practicing these advanced loop techniques and control statements to get a solid understanding of these concepts. They will come in handy when dealing with complex algorithms and data structures.
Additional JavaScript Functions (45 minutes)
Beyond the fundamental concept of a function, JavaScript offers additional functionality that can further streamline and enhance your code. We’ll explore closures, Immediately Invoked Function Expressions (IIFEs), and arrow functions.
Closures
Definition and Benefits
A closure in JavaScript is a function combined with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. The term “closure” refers to closing over variables from a higher scope.
Benefits of closures:
- Data privacy: The enclosed variables are only in scope within the containing (outer) function. They can’t be accessed from anywhere else, providing a form of data privacy.
- Function factories: We can create multiple function instances that operate on different data but use the same function body.
Practical Examples of Closures
Here’s an example of how closures work in JavaScript:
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('outerVariable:', outerVariable);
console.log('innerVariable:', innerVariable);
}
}
const newFunction = outerFunction('outside');
newFunction('inside'); // Logs: outerVariable: outside, innerVariable: inside
In this example, innerFunction is a closure that is returned from outerFunction. It has access to outerVariable from its outer function scope, even after outerFunction has finished execution.
Immediately Invoked Function Expressions (IIFE)
Explanation and Use Cases
An Immediately Invoked Function Expression (IIFE) is a JavaScript function that runs as soon as it is defined. The syntax is to enclose a function declaration in parenthesis and then call the function immediately with a second set of parenthesis.
Use cases of IIFEs:
- Avoid polluting the global scope by creating a private scope.
- They can be used to create a new variable scope.
Implementing an IIFE
Here’s an example of an IIFE:
(function () {
let message = 'Hello, World!';
console.log(message);
})();
This will log ‘Hello, World!’ to the console. The variable message is not accessible outside of the IIFE, keeping the global scope clean.
Arrow Functions
Syntax and Advantages
An arrow function is a concise syntax for writing function expressions in JavaScript. They use the “fat arrow” (=>) operator and do not need the function keyword.
Advantages of arrow functions:
- Shorter syntax
- No binding of
this: In arrow functions,thisretains the value of the enclosing lexical context’sthis.
Arrow Functions vs. Regular Functions
Here’s an example of how arrow functions work in JavaScript:
// Regular function
let square = function (x) {
return x * x;
};
// Arrow function
let squareArrow = (x) => x * x;
In this example, square is declared as a regular function, while squareArrow is declared as an arrow function. Both functions do the same thing – they return the square of x.
Exercises on Closures, IIFE, and Arrow Functions
Try the following exercises to get more comfortable with these concepts.
- Create a function
adder(x)that returns a function that addsxwith the argument it receives.
function adder(x) {
return function (y) {
return x + y;
};
}
const add5 = adder(5
);
console.log(add5(3)); // Output: 8
- Rewrite the following regular function into an IIFE.
(function() {
let x = 'Hello, World!';
console.log(x);
})();
- Convert the following regular function into an arrow function.
let greet = name => `Hello, ${name}!`;
console.log(greet('John')); // Output: Hello, John!
These exercises should help solidify your understanding of closures, IIFEs, and arrow functions in JavaScript. They are a crucial part of the language and understanding them is vital to becoming a skilled JavaScript developer.