Module 3: JavaScript in the Wild

Destructuring Assignment

Now that you are comfortable with JavaScript fundamentals, you will learn how to destructure, or break apart, objects and arrays to extract specific values and create shallow copies of them. Not only does destructuring save time and keystrokes, but it also makes your code easier to read! Destructuring allows you to make changes to an object or array without modifying the original data.

Let's say you are building an e-commerce website, and you have an array of products, where each product is an object with properties like name, description, price, and availability. To display the list of products on the homepage, you may need to extract only specific properties, such as the name and price, to show in a product grid. You can easily access the name and price properties from each product object in the array by using the destructuring assignment. Similarly, you may need to extract specific elements from an array if you have different colors of a single product to display the options for a customer to choose from during the checkout process.

By the end of this core competency, you will be equipped with the skills to work with complex data structures effectively, making you more efficient and confident as a software engineer. Ready? Let's get started!

Destructuring assignment with objects

Destructuring an object in JavaScript is a way to access one or more values from an object and assign them to variables in a single statement. At some point in your programs, you will want to extract some properties into their own variables with the same name. Thinking back to dot notation from object properties, you can use dot notation to access each value, but that can get repetitive.

Let's revisit accessing properties within an object without destructuring:


let obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }; 

let a = obj.a;
let b = obj.b;
let d = obj.d; 
                

To destructure an object, you will list the declaration (let or ) and the property names you want to extract between curly braces {} on the left-hand side of the assignment operator. On the right side of the assignment operator, you will use the name of the object that contains the properties you'd like to access.

Here is an example of a destructured object:


let { a, b, d } = obj; //destructuring the 'a', 'b', and 'd' properties from the obj object 
console.log(a, b, d); // prints 1 2 4
                

Pro Tip: The syntax on the left of the assignment operator is not an actual object, it just mimics its shape. This is called the destructuring binding pattern. You can reference more in-depth information about destructuring patterns here.

How to Build It

Your team Lead is building out the new charting app. To develop this further, you need to destructure the patient objects to only show the information needed on the top of the medical chart, which is the name, birthday, allergies, and diagnosis.

You can destructure an object like this:


let patient = {name: 'Jordan Fisherman', birthday: 'April 7, 1994', age: 29, allergies: ['gluten', 'penicillin'], diagnosis: 'Type 2 Diabetes', insurance: 'none', medicineLogs: [{medicine_name: 'Prednisone', dose: '20mg', time: '9pm'}]};

let {name, birthday, allergies, diagnosis} = patient;

console.log(name, birthday, allergies); // prints 'Jordan Fisher', 'April 7, 1994', ['gluten', 'penicillin']
                

The team can now show relevant information needed for the patient with less load time. This is a huge help for your senior developer who will be optimizing the logs on the medical chart. Great job!

Destructuring an array is similar to destructuring an object. You may also need to extract some elements into their own variables. Thinking back to square bracket notation from accessing array elements, you can use the square bracket notation to access the array elements, but it can also get repetitive. Let's revisit accessing elements within an array without destructuring:


let arr = [1, 2, 3]; // we have an array

let first = arr[0];
let second = arr[1];
                

To destructure an array, you will use a declaration, then the element names you want to extract between square brackets [] (not {}) on the left side of the assignment operator. On the right side, you will use the name of the array that contains the elements you'd like to access. Here is an example of a destructured array:


let [first, second] = arr; 
console.log(first, second); // prints 1 2                   
                

The syntax on the left of the equals is not an actual array, it just mimics its shape like its object counterpart.

Destructuring also provides a shorter way to swap the values of two variables, like this:


let [x, y] = [1, 2]; // declaring and assigning x and y
console.log(x, y); // prints 1 2
[x, y] = [y, x]; // performing the swap
console.log(x, y); // prints 2 1
                

How to Build It

The new charting app your team has been working on at Rowanda Regional Medical Center has reduced load times by 30 percent! The team would now like to optimize the medicine logs on the patient charts to increase efficiency. Your task is to destructure the medicine logs array like this:

Our patient at the medical center has quite a few medications he needs, so here is only a portion of the logs:


let patient = {name: 'Jordan Fisher', birthday: 'April 7, 1994', age: 29, allergies: ['gluten', 'penicillin'], diagnosis: 'Type 2 Diabetes', insurance: 'none', medicineLogs: [
  {medicine_name: 'Prednisone', dose: '20mg', time: '9pm'},
  {medicine_name: 'Loratadine', dose: '10mg', time: '9pm'}, 
  {medicine_name: 'Omeprazole', dose: '40mg', time: '10pm'}
]};
                

Since we already have the patient's info at the top of the chart, we only need the medicine name, dose, and time the medicine was given. We will use what we know so far to destructure the log like this:


let [first, second, third] = patient.medicineLogs; //gives us each object set to a variable name

console.log(first, second, third); // prints each medicine log object
                

The nurses can now see the medicine logs much easier. This is super helpful because some of the patient’s medications are very strict on how far apart each dose should be taken. Your senior developer is able to submit this feature for final review by the end of the day. Awesome work!

You can also use destructuring assignment with function invocations. You may also need to extract some values that are returned in functions into their own variables. You can use the same destructured object or array syntax from return values in functions, but which you use depends on the type of data. Let's explore some examples together!

Here is a function that returns a person object:


function person() {
  return { name: 'Joe', age: 45 };
}
let { name, age } = person(); // pulling name and age props from the value of the invocation
console.log(name, age); // prints 'Joe' 45
                

Here is a function that returns a fruits array:


function fruits() {
  return ['apple', 'orange', 'pineapple'];
}
let [first, second] = fruits(); // pulling the first couple of elements from the value of the invocation
console.log(first, second); // prints 'apple' 'orange'
                

If a function invocation returns an array or object, we can use destructuring with the invocation on the right of the assignment operator.

Pro Tip: You will see this later on in the program when you start learning React. Make sure to practice to get comfortable with destructuring!

How to Build It

Now that your senior developer has submitted the medical chat for final review, your team lead would like you to begin working on destructuring a function's return value to make the patient intake process more efficient. This function takes in the patient's name, birthday, any allergies, and if they have insurance as parameters.

You can destructure a return value like this:


function patientIntake(name, birthday, allergies, insurance) {
return {name: name, birthday: birthday, allergies: allergies, insurance: insurance};
}

let { name, birthday, allergies, insurance } = patientIntake('Miranda Lynn', 'August 14, 1967', 'none', 'Employer Insurance'); //destructured values from the returned object you receive from invoking the function

console.log(name, birthday, allergies, insurance); // prints 'Miranda Lynn' 'August 14, 1967' 'none' 'Employer Insurance'                    
                

The nurses at the hospital can now see the patient's information populate into the medical chart faster because of your hard work. This can be a life-saving feature. Your senior developer and team lead will be very pleased with this. You are doing a fantastic job!

Creating shallow copies of arrays and objects

You will run into situations throughout your career where you need to manipulate an array while also referencing the original array. This can be easily handled by copying the array using the spread operator. The spread operator, put simply, is ... followed by what you'd like to shallow copy. Here is the spread operator in action:


function addNumberSeven(arr) {
  const shallowCopy = [...arr]; // "spreading" all the elements in arr into a new array
  shallowCopy.push(7);
  return shallowCopy;
}

const nums = [1, 2, 3];

const numsAndSeven = addNumberSeven(nums);

console.log(numsAndSeven);// prints [1, 2, 3, 7]

console.log(nums); // prints [1, 2, 3]                     
                

In this example, you want to add the number 7 to the nums array without directly changing the array. The function addNumberSeven creates a shallow copy of the array by using the spread operator on the arr parameter [...arr] setting that to a variable. Because of this, any array that is passed into this function is created into a shallow copy. The data is safe!

Note: These copies are called "shallow" because if the array contains arrays or objects, you are not creating copies of those.

The spread operator can be used to create shallow copies of objects as well. In this example, we are going to use an object within another object:


 // example of copying an object using the spread operator

const fruit = { name: 'apple' };

const meals = { snack: fruit };

const mealsCopy = { ...meals };

fruit.name = 'banana';

console.log(meals); // prints { snack: { name: 'banana' } }
console.log(mealsCopy) // prints { snack: { name: 'banana' } }                    
                

There is another way of creating shallow copies of objects, which is as follows:


const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = Object.assign({}, obj1); // {} is the "target" onto which the props of obj1 are copied                    
                

Pro Tip: One of the main principles of software engineering is to maintain data integrity, which means that the data should be accurate, consistent, and complete. It is crucial to not directly change data because it violates this principle and causes errors that can be difficult to debug.

How to Build It

Time for your last shift at Rowanda Regional Medical Center! Your Team Lead would like you to assist with creating a program that can copy patient medications so the hospital's research team can test potential treatment plans without any unwanted side effects. You can make a shallow copy of a patient's medications like this:


let medications = [escitalopram, loratadine, gabapentin, melatonin];

function addMedication(arr) {
  const treatments = [amoxicillin, omeprazole, ibuprofen, tylenol];
  let medsCopy = [...arr]; // "spreading" all the elements in arr into a new array
  medsCopy.push(treatments[1]);
  return medsCopy;
}

console.log(addMedication(medications)); //prints [escitalopram, loratadine, gabapentin, melatonin, amoxicillin]

console.log(medications); // prints  [escitalopram, loratadine, gabapentin, melatonin]                    
                

The research team can now test each potential medication for any interference before treating the patient! With this more efficient process, the hospital hopes to see a shorter wait time for patients to be treated. Your senior developer and team lead are proud of your amazing progress with the biomedical team. Congrats on completing this case study!

Conclusion

Excellent work completing this core competency on destructuring assignments! As you have explored this core competency, you have learned how destructuring saves time, keystrokes, and repetition within programs. You should be confidently destructuring from objects, arrays, and return values of functions. You also discovered how to create shallow copies of arrays and objects. We recommend that you keep practicing destructuring assignments because as you continue on your software developer journey, destructuring values will become another essential part of your workflow on the job.

The Ternary Operator

One popular shorthand operation is the ternary operator. The ternary operator takes the code of an if-else statement and squashes it into one line. If-statements are still helpful and can manage complexity far better than ternaries. Still, with more straightforward scenarios, you'll rarely see an if-statement used where a ternary operator would work.

Ternaries are popular among developers; you will often see them in your career. Developers like them because ternaries decrease the needed code and are often considered easier to read and write. It's worth your time to become familiar with ternaries so you understand them and speed up your development. Read on and see what ternaries can do for you.

Using the ternary operator

A ternary expression is a shorthand way of writing an if statement with only two possible outcomes. It takes the form of condition ? value if truthy : value if falsey.


let age = 20;
let message = age >= 18 ? “You are an adult”: “You are not an adult”;                    
                

The first part of the ternary (age >= 18) is the condition followed by a question mark, as if it's presenting the question in code. The second part is the value returned if the condition is truthy, and the third is if it's falsy. The above example reads, “if the age variable is greater than or equal to 18, the message variable will be set to 'You are an adult'. Otherwise, it will be set to 'You are not an adult'.”

Ternary expressions are best used for conditions that have only two possible outcomes. If statements, on the other hand, are better suited for more complex conditions with multiple outcomes. However, this is not a hard-and-fast rule. The choice between the two ultimately depends on the specific situation and the programmer's preference.

How to Build It

The website's designer wants a button on the top right corner of the page that allows users to either login or logout based on the user's login status. A variable holding the user's login state already exists on all pages (isUserLoggedIn). If they are logged out, the designers want the button to say “Login”. Otherwise, it should say “Logout”.


let isUserLoggedIn = false

// get the button element from the HTML. The button has the id “login-button”
const button = document.querySelector('#login-button');

// update the text of the button based on if the user is logged in
button.textContent = isUserLoggedIn ? 'Logout': 'Login';                    
                

This code is clear and succinct! If you changed the button text using an if-statement, the code would be longer and not add any extra value. You could argue that an if-statement makes the code less readable.


let isUserLoggedIn = false
const button = document.querySelector('#login-button');
if (isUserLoggedIn) {
    button.textContent = 'Logout'
} else {
    button.textContent = 'Login'
}                    
                

The designers have a second request. When the user hits the button, you need to perform two actions: call the appropriate function (login() or logout()) and update the button's text. Could a ternary be used here? Not easily. An if-statement would work better because you perform multiple actions in at least one of the branches. Let's see what that might look like.


button.addEventListener('click', () => {
  if (isUserLoggedIn) {
    logout()
    button.textContent = 'Login'
  } else {
    login()
    button.textContent = 'Logout'
});                    
                

When to use ternary operators is up to you, but your co-workers are impressed with the readability of your code and hope to see more of this clean coding style in the future.

Conclusion

Ternary operators are useful shorthand tools that are easy to read and write. They can eliminate many lines of unnecessary code and let you focus on the task rather than verbose syntax. Remember them as you write code, and you will see how valuable ternaries can be. And don't worry about finding opportunities to use them; you will have plenty of chances to use ternaries here at BloomTech and throughout your career.

Asynchronous Code

Welcome to the world of asynchronous programming! This is a major step in your coding journey. Web applications are typically asynchronous, meaning the program is not supposed to be executed sequentially or in a deterministic fashion. Instead, pieces of code are triggered according to events such as user interaction or timers. In fact, JavaScript has been conceived from the ground up to facilitate asynchronous programming. Understanding how asynchronous code works - types of events, event listeners and out-of-order execution - is a fundamental skill that will enable you to design real-world applications. You will now learn how to use timers and browser events to trigger functions in your program (often called “callback functions” or “event handlers”).

Asynchronous code using timeouts

The setTimeout function is the simplest intro to async programming, and yet very popular amongst developers. The concept is straightforward: a particular block of code is scheduled to be executed later rather than now. This is commonly done to implement a delay before displaying a message to the user, but there are many possible use cases for a timer.


// the "future" code is always packaged, or "wrapped" in a function
function delayedCode() { 
  console.log('delayed code');
}

console.log('foo');

// the first argument is the function declared previously, the second is a delay in milliseconds
setTimeout(delayedCode, 10); 
console.log('bar');

/* the order of prints to the console is:
foo
bar
delayed code */                    
                

Once the delay is done, the JavaScript engine calls the function passed as the first argument. But the other two log statements happen sequentially (synchronously). We can also "inline" the function containing the "future" code, like in this next example:


console.log('fizz');
// the future code, in this case the log statement, is wrapped in an anonymous function
setTimeout(() => console.log('delayed code'), 1); 
console.log('buzz');

/* the order of prints to the console is:

fizz
buzz
delayed code */                    
                

To put it in perspective, 1ms is usually a LOT of time in terms of engine execution, surely enough or two console.log executions.

How to Build It

The Perereka club would like to offer more services to its members. By incorporating a web server in the setup, the club will be able to gather real time weather and traffic data from the Internet every 30 minutes, and broadcast the message to all users. This is how the code would look like:


// 1800000 ms = 30 min
setTimeout(broadcastUpdates, 1800000);

function broadcastUpdates() {
    // This will check if there's anyone currently using the frequency, and delay the broadcast for another 2 minutes (120 seconds, or 120000 milliseconds)
    if (isFrequencyBusy()) {
        setTimeout(broadcastUpdates, 120000);
        return; //nothing else to do now, exit the function
    }

    // Function to create the audio file from the updated weather and traffic data
    const updates = createAudioFrom("latest_report.txt");
    // Function to broadcast the message. In practice, this will output the computer's audio into the transmitter located at the repeater tower
    transmitAudio(updates);
    // Let's no forget about the smartphone users connected to the website
    streamAudio(updates);
    // Now we are ready to reschedule (30 minutes)
    setTimeout(broadcastUpdates, 1800000);
}                   
                

The first setTimeout needs to be called in the “main” code body, otherwise the first run will never be scheduled. But notice how we are rescheduling the callback from within the same function! That's because once the callback is executed, it will never run again until you “register” it into a new setTimeout. In other words, setTimeout is a “run-once” trigger.

In certain cases, you might want to schedule a function to run periodically, with a constant delay between runs, and regardless of external conditions. JavaScript offers you the setInterval function, exactly for that purpose:


setInterval(() => {
  console.log("beat")
}, 1000);                    
                

By the way, the callback function can be declared anywhere in the code since its position is not related to the order of execution.

When an async function runs, it takes full control over the execution engine. That implies that no other code will be triggered until the current one is finished. If the code executing is too long or slow, the application will be “blocked.” A good design pattern is to try and keep the callbacks as simple and short as possible. For more complex scenarios, advanced techniques in JavaScript - such as async, await and promises - enable you to create “concurrent” non-blocking execution.

Let's move on! The setTimeout function is an example of an event listener. It is particularly “listening” for the timer to say “Go!”. In the next Learning Objective, you will learn how to use other types of event listeners, which will “listen” to user actions such as “I clicked!”.

Asynchronous code using browser events

The browser interface often requires the user to click on buttons or input text that will be processed by the application. This is a typical asynchronous scenario where we don't know when the user will interact with the browser. The setTimeout function is not going to work here. Instead, we need a way to trigger code after a particular user interaction happens in the webpage. Just like we did with the setTimeout listener, we will set up browser event listeners to trigger our functions:


window.onclick = () => console.log('Ha! You clicked'); 
// if you click repeatedly on the browser window you'll see 'Ha! You clicked' printing each time                   
                

JavaScript programs are more of an orchestration of "future" reactions to certain events than a series of synchronous actions. This is because users "will act when they act," not right when the program starts on page load. Here are some other examples of browser events that can trigger callbacks: mouse over a particular page element, text copied to the clipboard, specific key typed on the keyboard, and display entered the fullscreen mode.

How to Build It

A member of the radio club wants to connect to Perereka's web application through her smartphone (phone's browser) to participate in a conversation. It would be great if all the user had to do was press a button and start talking.

This is how the code would look:


setTimeout(broadcastUpdates, 1800000);
function broadcastUpdates() {
 // This will check if there's anyone currently using the frequency, and delay the broadcast for another 2 minutes (120 seconds, or 120000 milliseconds)
 if (frequencyBusy()) {
   setTimeout(broadcastUpdates, 120000);
   return;
 }
 // Function to access the city's API and get the data in JSON format
 getCityData();
 // Function to create the audio file from the text received
 createAudio("updates.mp3");
 // Function to broadcast the message. In practicce, this will output the audio into the transmitter mic and trigger the PTT (push to talk).
 transmitAudio("updates.mp3");
 // Now we are ready to re-schedule for 30 minutes!
 setTimeout(broadcastUpdates, 1800000);
}
talkButton.ontouchstart = startTalk;
talkButton.ontouchend = finishTalk;
// Assume that this object (talkButton) has been created somewhere else in the code, and that it represents a button in the user interface.
// Just like you can add an event listener to the window object, we can add the previous two event listeners to the button object.
// They will monitor touch events triggered by the user's touchscreen on a cell phone!
function startTalk() {
 // This will check if there's anyone currently using the frequency, and avoid the pile-up.
 if (frequencyBusy()) {
   return;
 }
 //Might add code here to change the button color to red!
 // Function to broadcast the message. In practice, this will stream the phone's mic to the web server, which will in turn output the audio into the transmitter mic and trigger the PTT (push to talk).
 startStreaming();
}
function finishTalk() {
 //Might add code here to change the button color to gray!
 stopStreaming();
}                    
                

function startTalk() {
 
// This will check if there's anyone currently using the frequency, and avoid the pile-up.
 if (frequencyBusy()) {
   return;
 }
 //Might add code here to change the button color to red!
 // Function to broadcast the message. In practice, this will stream the phone's mic to the web server, which will in turn output the audio into the transmitter mic and trigger the PTT (push to talk).
 startStreaming();
}function finishTalk() {
 //Might add code here to change the button color to gray!
 stopStreaming();
}                    
                

In this last example, we are using two different events and two corresponding callback functions. The event listeners (touchstart and touchend) are associated with the same page element talkButton. You will learn how to create page elements later in the course.

Conclusion

By using timers and other types of events, you will be able to design very powerful async web applications! While traditional synchronous code has little use to web applications, it is in fact easier to write, read and debug, as compared to async code. If this is your first experience with async programming, it might take you some time to develop a new mindset. Keep practicing! Experiment with multiple setTimeouts and callbacks. And if you feel adventurous, see what happens when you call other sync and async functions from inside a callback. Remember that console.log is your best friend to learn and debug.

Guided Project

In this guided project, the instructor explains how to use destructuring, ternary operators, shallow copies, and asynchronous code in JavaScript. Many developers have used these techniques to make their code more efficient and easier to read, which means you'll be working with these techniques in your career. This video will walk through how and when to use each concept so you can make your code more efficient and level up your dev skills.

Module 3 Project: JavaScript in the Wild

Understanding destructuring and ternaries in JavaScript is vital for writing clear and concise code. They help developers write code that is easy to read and maintain which in turn helps to improve collaboration and productivity within teams working on the same project. This module project provides a variety of challenges to help enhance your skills and comprehension.

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: JavaScript in the Wild

  • 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.

Solution

Additional Resources