Module 2: Advanced Functions
Arrow Functions
As you advance in your experience with Javascript, you will undoubtedly come across, and hopefully use, more and more "shortcuts". As new versions of Javascript get released, more shortcuts are created allowing for developers to do the same thing but with less code. This might not seem like much, but if you are developing an app that runs tens of thousands of lines of code, every shortcut counts towards better performance.
All that to say, the 2015 revision of Javascript introduced many new features including one of the most frequently used shortcuts in all of Javascript: the "arrow function".
In this reading, you will learn more about the syntax of arrow functions, how to use them, and when not to use them.
Using arrow function syntax
Arrow functions operate almost like standard functions, just with a different syntax. A standard function expression with no parameters looks like the following:
const greet = function() {
return "Hello" // when executed will return "Hello"
}
A one-line arrow function will have the same logic, but shorter code:
const greet = () => "Hello" // the value right after the arrow gets returned
As you might have been able to spot, arrow functions, first
, omit the word "function" but still
keep the parentheses if there are no parameters.
Secondly
, they add an "arrow function" symbol which is an "=" sign and a ">" sign together:
"=>".
Finally
, the word "return" need not be included if there are no curly brackets: the value right
after the arrow gets returned. Let's look at another example, but with a parameter.
First, a standard function would look like this:
const printArg = function(arg) {
console.log(`What is the arg? It is ${arg}`)
}
And an arrow function would shorten it a bit to:
const printArg = arg => console.log(`What is the arg? It is ${arg}`)
Similar to before, except that when using a parameter, you can optionally take out the parentheses. In both
syntax versions, printArg
is printing a value to the console and returning undefined. When using
the curly brackets to wrap one or more lines of code in the body of the function, the keyword "return" is no
longer implied and needs to be included again for arrow functions.
See example:
const exampleFunction = () => {
const year = new Date().getFullYear() // added line of code
return `The current year is: ${year}` // "return" keyword needs to be included
}
How to Build It
Returning to your new gig with "JavaSplit" and revamping their code. You first notice a plethora of traditional functions that could certainly shave off a few characters. You add a fictional customer profile, Jonas Korhonen, to check your work as you go.
Here is the original function:
const user = { // fictional user info who might be shopping on the website
name: "Jonas Korhonen",
email: "coffee4life@gmails.com",
cart: ["Colombian Beans", "JavaSplit Sweet Cream"],
}
const addItemToCart = function(item) { // traditional function that adds item
return user.cart.push(item) // adds item to cart, returns new cart length
}
addItemToCart("Holiday Mug") // adding "Holiday Mug" to cart
console.log(user.cart) // will print ["Colombian Beans", "JavaSplit Sweet Cream", "Holiday Mug"]
You convert the traditional function above to an arrow function:
const addItemToCart = item => user.cart.push(item); // new arrow function
As expected, your changes included omitting the keyword "function", omitting the parentheses (because this function has a parameter) and omitting the keyword "return" because they are implied in arrow functions with one line in their body and no curly brackets. Finally, you add one thing, which is the arrow function symbol "=>" after the argument.
Arrow functions and the this keyword
This will be a relatively short section since it touches on only one key point: you should not use arrow functions as methods in objects. Please use traditional functions instead. Consider the example below that uses a traditional function:
const sampleObject = {
sampleKey: "sampleValue",
sampleMethod() {
return `Let's print out the ${this.sampleKey} property value!`
}
}
console.log(sampleObject.sampleMethod()) // prints "Let's print out the sampleValue property value!"
Methods require variables that reference internal properties to include the this
keyword. This
works for traditional functions like the one above but not with arrow functions. If we were to convert the
method above to an arrow function like so:
sampleMethod: () => {
return `Let's print out the ${this.sampleKey} property value!`
}
...and try calling the method again; it would instead print out, "Let's print out the undefined property value!"
Here's why, in short: for arrow functions, the "this" keyword refers to other global properties, not the properties of the local object, which an arrow function inherits but traditional functions do not. To illustrate this concept more clearly, let's look at the "How To Build It" section.
How to Build It
Fantastic news! You are flying through the old code of "JavaSplit", converting all those traditional functions to arrow functions. Unbeknown to your auto-pilot brain, you also swap out a method within the user object "changeEmail" with arrow functions too! See here our fictional user's, Jonas', info :
const user = {
name: "Jonas Korhonen",
email: "coffee4life@gmails.com",
cart: ["Colombian Beans", "JavaSplit Sweet Cream", "Holiday Mug"],
changeEmail: (newEmail) => { // syntax for a method that has an arrow function
this.email = newEmail
}
}
console.log(user.email) // prints "coffee4life@gmails.com
When testing the code, you try updating Jonas' email with the "changeEmail" method and printing the result to the console:
user.changeEmail("ih8coffenow@gmails.com")
console.log(user.email) // still prints "coffee4life@gmails.com
You quickly realize your mistake and return the arrow function back to a traditional function then call the method again:
const user = {
name: "Jonas Korhonen",
email: "coffee4life@gmails.com",
cart: ["Colombian Beans", "JavaSplit Sweet Cream", "Holiday Mug"],
changeEmail (newEmail) {
this.email = newEmail
}
}
user.changeEmail("ih8coffenow@gmails.com")
console.log(user.email) // now prints "ih8coffeenow@gmails.com"
Conclusion
Arrow functions help make your code succinct. You will find throughout your career many times when a quick funciton is needed, and you will use an arrow function to quickly code it and move on. Be careful not to use arrow functions in objects as that can cause some unintended consequences, but otherwise, when a short function is all you need, the arrow function let's you write it and get on with your code.
Working with Arguments
So far, you have learned how to write functions that accept a specified number of arguments. Functions with a specified number of parameters is probably the most common format you will see here at BloomTech and in your careers; however, there will be many situations when you must write functions that can handle an unknown number of arguments.
Functions that accept an unknown number of arguments are essential for two reasons. One reason is that some functions will expect an unknown number of arguments. Suppose you wrote a function that would add up all the numbers passed to it as arguments, regardless of how many there are. Five numbers or 500, the function would work just fine. The second reason is functions that accept an unknown amount of arguments can be useful when handling user errors, as in, what if the user accidentally passes too many or too few arguments than the function needs?
As you can imagine, there are solutions in Javascript to both of these problems. In this reading, you will learn how to write functions that can handle an unknown amount of arguments, both when the function calls for it and when the function receives too many or too few arguments than expected.
Working with a variable number of arguments
Writing functions that handle a variable number of arguments can primarily be accomplished in two ways: using the "arguments object" or using "rest parameters."
Built into traditional Javascript functions (i.e., not arrow functions), the "arguments object" uses the keyword "arguments" in the body of the function. The "arguments" keyword holds an array-like object that references all the values or arguments passed into the function, no matter how many there were. This array-like object automatically gets created when the function gets called. See the example below:
function sampleFunction () {
console.log(arguments) // arguments is an array-like structure holding the arguments
}
sampleFunction("Good", "Bad", "Ugly") // prints ["Good", "Bad", "Ugly"]
Like a standard array, the arguments object assigns all the values that were passed to the function to numbered indices, beginning with "0". These indices can be used to access the values of the arguments using loops, as in:
function sampleFunction() {
for (let idx = 0; idx < arguments.length; idx++) {
console.log(arguments[idx])
}
}
sampleFunction("Good", "Bad", "Ugly") // prints "Good", "Bad", "Ugly"
Notice how the keyword "arguments" is used. Using any other word, like you can with standard parameters, will not work. Also notice that the keyword "arguments" does not appear in the parentheses of the function.
Another option to handle an unknown amount of arguments in a function is to use something called a "rest parameter". A rest parameter will essentially do the same thing as an argument object: create an array of the values passed to to the function as arguments. See example here:
function sampleFunction(...args) { //note the three dots
console.log(args) // args is an array that lists the values you pass as arguments to the function
}
sampleFunction(1, 2, 3) // prints [1, 2, 3]
There are a few syntactical differences when using the rest parameter versus the argument object.
- You can use any word for the rest parameter, just like a standard parameter. We chose to use the word "args" in this example.
- You must include the spread operator, which is three dots "...", before the rest parameter in the parentheses (not the body of the function)
- You can use a rest parameter in arrow functions, but you must include the parentheses around the spread operator and declared variable as you would in a traditional function.
How to Build It
The time has come for you to write a function that will receive an unknown amount of arguments for your friend's e-commerce website, "Body Kare". You would like to write a function that includes a "for" loop and will add up the prices of all the items in a customer's cart. Of course, the amount of items in the cart can vary, which is why you need to write a function that uses either an "arguments object" or a "rest parameter". We will write a function for each, so that you can compare.
For a function that uses an arguments object, you would write the following:
function addPricesInCart() { //note that no parameter is specified
let totalPrice = 0
for (let i = 0; i < arguments.length; i++) { //looping through all the prices passed as arguments
totalPrice += arguments[i] //adding each price to totalPrice
}
console.log(`Total items: ${arguments.length}; Total price: ${totalPrice}`)
}
Notice how the code uses a "for" loop to iterate through all the prices that were passed as arguments into the function. Treating the function's arguments object like any array, you were able to run the loop for as many values found in the array-like object by using "prices.length". For each price, you then added that value to the total price.
When called, let's see how this function handles different amounts of arguments:
addPricesInCart(4.49, 5.99, 2.99) // prints "Total items: 3; Total price: 13.47"
addPricesInCart(2.23, 1.99, 14.24, 3.45) // prints "Total items: 4; Total price: 21.91"
addPricesInCart(3.67) // prints "Total items: 1; Total price: 3.67"
Now let's look find a solution to the same scenario, but using a "rest parameter" instead:
function addPricesInCart(...prices) { //note the three dots
let totalPrice = 0
for (let i = 0; i < prices.length; i++) { //looping through all the prices passed as arguments
totalPrice += prices[i] //adding each price to totalPrice
}
console.log(`Total items: ${prices.length}; Total price: ${totalPrice}`)
}
addPricesInCart(4.49, 5.99, 2.99) // prints "Total items: 3; Total price: 13.47"
Same as before, you were able to use a standard "for" loop to iterate through all the prices that were passed as arguments into the function. Treating the "prices" argument as an array, you were able to use the key term "prices.length" to accommodate for any amount of arguments that may have been passed. It's worth pointing out that the code ended with the same result as the function that used an arguments object instead. There's never just one way to do something in coding.
Working with default arguments
Some functions, by design, expect the possibility of receiving fewer arguments than called for. Often these
omitted arguments were designed to be optional and would receive "default" values if no argument is passed
into the function for them. To set up a default value for a parameter, you need to add an equal sign,
=
, followed by a value just after the name of the parameter set in the parentheses of the
function, as in:
function sampleFunction(sampleParameter = "Hello Anyway") { //setting default value of sampleParameter to "Hello Anyway"
console.log(sampleParameter)
}
sampleFunction("Hello") // will print "Hello" because argument was passed
sampleFunction() // will print "Hello Anyway" because no argument was passed and "Hello Anyway" is the default value
You can also do this for multiple parameters within one function as in:
function addUp(num1 = 1, num2 = 2) {
console.log(num1 + num2)
}
addUp(1, 4) // prints 5, adding both arguments together
addUp(6) // prints 8, using 6 for the first argument and the default value of 2 for the second
addUp() // prints 3, using default values for both arguments
Please note that if no default value is given for a parameter and there was no argument passed to the function for that parameter, an automatic value of "undefined" will be used instead.
How to Build It
Let's return to the e-commerce website you are building for your friend's new company, "Body Kare". You are now looking to write a function that will allow for your friend to add an item to their online store whenever they release a new product. Since each new product will have a few details to go along with it, such as: name, price, items in stock, etc., you will need to create a function that accepts multiple parameters and returns an object whose properties reference all those values. Furthermore, there is always the possibility that your friend might not have some of those details yet, so you should include defaults for each. Here is what that would look like:
function addProduct(name, price = 0, quantity = 0) { // establishing parameters, setting default values to two of them
let productObject = {name: name, price: price, itemsInStock: quantity} // creating an object to return
return productObject
}
console.log(addProduct('shampoo', 4.99)) /* prints:
{
name: "shampoo",
price: 4.99,
itemsInStock: 0
}
*/
In this example, the quantity of items in stock was not included as an argument when called. Thus, the default value of "0" was added to the object for that property.
It's worth pointing out that all arguments received by functions are the values only, and that the order that these arguments are inputted does determine which values gets placed with which parameters. Using the example above, if you wanted to create a product item for "shampoo", but wanted to use the default value of the price and not the quantity, you could not with this approach. There are ways to work around this, but they will not be discussed here.
Conclusion
You have just covered how to have a variable number of arguments and add default arguments. This is an impressive level of understanding as many developers are not aware that functions can do this until years into their careers (and they all wish they had learned it sooner). Being able to add these options to your arguments allows your code to be more flexible and more powerful. Functions with variable number of arguments communicates that this function can handle a lot of data. Functions that have default values allow developers to be so concerned with function arguments and move onto more important matters. Code that enables developers is empowering, and you'll learn more about how to empower yourself and other developers in future lessons. Keep it up!
Functions as Arguments
Welcome to functions as arguments in JavaScript! You have already mastered some fundamentals like objects, classes, if statements, and loops, and you're now ready to dive deeper with functions as arguments. Functions as arguments are powerful tools that allow you to perform operations on a collection of data or iterate over an array more efficiently.
For example, if you worked at a fitness club, you could perform different actions on club member information using multiple functions. You could use the map function to apply a discount formula to each element in the array and create a new array with the cheaper member price. You could use the forEach function to iterate over the array and greet each member by name when they scan into the club. Lastly, you could use the filter function to create a new array with only the members that meet your criteria to target upsells like personal training.
In this core competency, you will learn the three most commonly used functions as arguments- map, forEach, and filter. Remember, functions as arguments are just one of the many tools at your disposal, and as you navigate through this core competency, you'll discover new and exciting ways to use them. Let's get started!
Using forEach
The forEach
method allows you to perform a specific operation on each element in an array. It
expects a callback function, which is a function passed into another function as an argument. The function
passed into forEach
must take an array element as a parameter. The function can either be fully
defined between the parentheses or be the name of an existing function. Then, just like any other function,
you can add a statement for the function to execute. Let's investigate the structure of the
forEach
function:
// using forEach with a normal function
let friends = ['Jane', 'Peter', 'Ada'];
friends.forEach(function (friend) {
console.log(friend); // the statement inside the block executes for each string inside the array
});
/* prints
Jane
Peter
Ada */
When using functions as arguments, it is good practice to use arrow function syntax. Arrow functions condense the amount of code you write, making it easier to read.
// using forEach in an arrow function
let friends = ['Jane', 'Peter', 'Ada'];
friends.forEach(friend => console.log(friend)); // reads almost like English!
/* prints
Jane
Peter
Ada */
Pro Tip: Many array methods expect a function to be passed into it. This array cheatsheet is super helpful when using functions as arguments within arrays.
How to Build It
Back at Gardine, your team lead is excited to start building out an inventory function for crops farmers have available. There is already an array of crops, but we need to have a count of how many crops farmers have. To develop this inventory further, you need to create a test function that shows restaurants what crops are available and the total number of crops.
Here is our crops array:
const crops = ["corn", "tomato", "potato", "lettuce", "cabbage", "carrot"];
You can do this using forEach
like this:
crops.forEach(function (crop) {
console.log(crop); //prints each crop in order
console.log(crops.length); //shows the total number of crops
});
The team now has a starting point for displaying each crop for restaurants and the total number of crops available. This new functionality is a massive help for your senior developer, who will build out the selection process. Great work!
Using filter
The filter
method allows you to create a new array with elements that meet specific criteria.
The function passed into the filter
method must return a truthy value for adding an element to
the new array. Let's take a closer look at the syntax:
// using filter
let numbers = [1, 24, 3, -5, 49, 38, 5, 7]; // the "old" array
let biggerThanTen = numbers.filter(number => number > 10); // the "new" array
console.log(biggerThanTen); // prints [24, 49, 38]
The function within filter (number => number > 10
) is invoked as many times as there are
elements in the old array. Each array's element is sent into the function as an argument and evaluated. The
new array will only include the elements where the function returns a truthy value.
Remember it's important to store the result of filter in a variable so it can be used later.
How to Build It
The implementation team at Gardine has been able to expand on the crops array, providing more information for restaurants about each crop. The team would now like to build the ability to filter only the crops restaurants need to restock. Your task is to create the filter function from this array:
const crops = [{name: "corn", type: "vegetable"}, {name: "tomato", type: "produce"}, {name: "potato", type: "vegetable"}, {name: "lettuce", type: "produce"}, {name: "cabbage", type: "produce"}, {name: "carrot", type: "vegetable"}];
You can filter to only produce by using .filter like this:
function filterByType(type) {
return crops.filter(crop => crop.type === type);
}
Restaurants can now filter by the type of crop they need to restock. This ability is helpful because produce has a shorter shelf life, so restaurants order it more often. Your senior developer can add this to their selection flow. Awesome Job!
Using map
The map
method allows you to apply a specific operation to each element in an array and create a
new array with the results. Occasionally, you'll even use map
to create different objects from
those in the first array. The syntax will look familiar, like in this example:
// using map
let numbers = [1, 2, 3, 4, 5]; // the "old" array
let numSquared = numbers.map(number => number ** 2); // the "new" array
console.log(numSquared); // prints [1, 4, 9, 16, 25]
The function within the parentheses (number => number ** 2) is invoked as many times as there are elements in the old array. For each invocation, the "current" element in the array being iterated over is sent into the function as the argument. Whatever value returns from the function is included as the corresponding element in the new array.
Remember it's important to store the result of using map
in a variable, so it can be used later.
How to Build It
Time for your last shift at Gardine! Now that your senior developer has built out the selection flow for restaurants to add crops to their cart, your Team Lead would like you to add the functionality to add multiples of their selected crops from within the cart. You can easily add this functionality with map like this:
Here is a test cart for you to test:
const cart = [{name: "tomato", count: 5}, {name: "lettuce", count: 2}, {name: "cabbage", count: 3}];
let addToCart = cart.map(item => {
item.count++; //adds 1 to each count of the crop in the cart
return item; // remember to return a value, it becomes the corresponding item in the new array
});
console.log(addToCart); //prints [{name: "tomato", count: 6}, {name: "lettuce", count: 3}, {name: "cabbage", count: 4}];
Restaurants can now add multiples of each crop to their cart with a single button! The team can build upon this functionality for future updates. Your senior developer and team lead are very impressed with the fantastic work you have accomplished at Gardine. Congrats on completing this case study!
Conclusion
forEach
, filter
, and map
are powerful functions that let us iterate,
manipulate, and even create new arrays. You have learned how to use them in this reading, but as you start
thinking about arrays knowing that you can filter and map them, you may surprise yourself with how many ways
you can leverage them. Great job with this reading and keep it up!
Guided Project
Welcome to this guided project, where we'll explore powerful JavaScript techniques for array manipulation,
including forEach
, map
, and filter
. These methods are just a few
examples of how you can efficiently and concisely transform raw data into valuable insights. As you engage
with this video, you will gain hands-on experience in implementing these techniques, which are essential for
your work at BloomTech and throughout your career. Mastering array manipulation will empower you to easily
handle large volumes of data, enabling you to make a lasting impact in your professional endeavors. Let's get
started on this exciting journey!
Module 2 Project: Advanced Functions
Arrow functions make your code more efficient and readable and improve collaboration within a team of developers by promoting a consistent and standardized codebase. You will become more comfortable and proficient in writing and reading code that utilizes these concepts through the challenges in this module project.
The module project contains advanced problems that will challenge and stretch your understanding of the module's content. The project has built-in tests for you to check your work, and the solution video is available in case you need help or want to see how we solved each challenge, but remember, there is always more than one way to solve a problem. Before reviewing the solution video, be sure to attempt the project and try solving the challenges yourself.
If you can successfully get through all the Module Projects in a sprint, you are ready for the Sprint Challenge and Sprint Assessment, which you must pass to move on to the next Sprint.
Instructions
The link below takes you to Bloom's code repository of the assignment. You'll need to fork the repo to your own GitHub account, and clone it down to your computer:
Starter Repo: Advanced Functions
- Fork the repository, and then use the green button and your terminal to clone the project down to your computer.
- Next, open the
index.html
file in both VSCode and the Chrome Browser. VSCode is where you will make code changes, and Chrome is where you will check if your code is passing all the tests. - To check the tests, right-click on a Chrome window, select "Inspect," and ensure the console is visible. Then return to VSCode and start coding a solution to the first challenge. When you want to check if your code is correct, go to Chrome and refresh the page to run the tests and see your results.