Book Image

Advanced JavaScript

By : Zachary Shute
Book Image

Advanced JavaScript

By: Zachary Shute

Overview of this book

If you are looking for a programming language to develop flexible and efficient applications, JavaScript is an obvious choice. Advanced JavaScript is a hands-on guide that takes you through JavaScript and its many features, one step at a time. You'll begin by learning how to use the new JavaScript syntax in ES6, and then work through the many other features that modern JavaScript has to offer. As you progress through the chapters, you’ll use asynchronous programming with callbacks and promises, handle browser events, and perform Document Object Model (DOM) manipulation. You'll also explore various methods of testing JavaScript projects. In the concluding chapters, you'll discover functional programming and learn to use it to build your apps. With this book as your guide, you'll also be able to develop APIs using Node.js and Express, create front-ends using React/Redux, and build mobile apps using React/Expo. By the end of Advanced JavaScript, you will have explored the features and benefits of JavaScript to build small applications.
Table of Contents (9 chapters)

Destructuring Assignment


Destructuring assignment is syntax in JavaScript that allows you to unpack values from arrays or properties from objects, and save them into variables. It is a very handy feature because we can extract data directly from arrays and objects to save into variables, all on a single line of code. It is powerful because it enables us to extract multiple array elements or object properties in the same expression.

Array Destructuring

Array destructuring allows us to extract multiple array elements and save them into variables. In ES5, we do this by defining each variable with its array value, one variable at a time. This makes the code lengthy and increases the time required to write it.

In ES6, to destructure an array, we simply create an array containing the variable to assign data into, and set it equal to the data array being destructured. The values in the array are unpacked and assigned to the variables in the left-hand side array from left to right, one variable per array value. An example of basic array destructuring is shown in the following code:

let names = [ 'John', 'Michael' ];
let [ name1, name2 ] = names;

console.log( name1 ); // Expected output: 'John'
console.log( name2 ); // Expected output: 'Michael'

Snippet 1.37: Basic array destructuring

As can be seen in this example, we have an array of names and we want to destructure it into two variables, name1 and name2. We simply surround the variables name1 and name2 with brackets and set that expression equal to the data array names, and then JavaScript will destructure the names array, saving data into each of the variables.

The data is destructured from the input array into the variables from left to right, in the order of array items. The first index variable will always be assigned the first index array item. This leads to the question, what do we do if we have more array items than variables? If there are more array items than variables, then the remaining array items will be discarded and will not be destructured into variables. The destructuring is a one to one mapping in array order.

What about if there are more variables than array items? If we attempt to destructure an array into an array that contains more variables than the total number of array elements in the data array, some of the variables will be set to undefined. The array is destructured from left to right. Accessing a non-existent element in a JavaScript array results in an undefined value to be returned. This undefined value is saved to the leftover variables in the variable array. An example of this is shown in the following code:

let names = [ 'John', 'Michael' ];
let [ name1 ] = names
let [ name2, name3, name4 ] = names;

console.log( name1 ); // Expected output: 'John'
console.log( name2 ); // Expected output: 'John'
console.log( name3 ); // Expected output: 'Michael'
console.log( name4 ); // Expected output: undefined

Snippet 1.38: Array destructuring with mismatched variable and array items

Note

We must be careful when destructuring arrays to make sure that we don't unintentionally assume that a variable will contain a value. The value of the variable could be set to undefined if the array is not long enough.

ES6 array destructuring allows for skipping array elements. If we have an array of values and we only care about the first and third values, we can still destructure the array. To ignore a value, simply omit the variable identifier for that array index in the left-hand side of the expression. This syntax can be used to ignore a single item, multiple items, or even all the items in an array. Two examples of this are shown in the following snippet:

let names = [ 'John', 'Michael', 'Jessica', 'Susan' ];
let [ name1,, name3 ] = names;
// Note the missing variable name for the second array item
let [ ,,, ] = names; // Ignores all items in the array

console.log( name1 ); // Expected output: 'John'
console.log( name3 ); // Expected output: 'Jessica'

Snippet 1.39: Array destructuring with skipped values

Another very useful feature of array destructuring is the ability to set default values for variables that are created with destructuring. When we want to add a default value, we simply need to set the variable equal to the desired default value in the left-hand side of the destructuring expression. If what we are destructuring does not contain an index to assign to the variable, then the default value will be used instead. An example of this is shown in the following code:

let [ a = 1, b = 2, c = 3 ] = [ 'cat', null ]; 
console.log( a ); // Expected output: 'cat'
console.log( b ); // Expected output: null
console.log( c ); // Expected output: 3

Snippet 1.40: Array destructuring with skipped values

Finally, array destructuring can be used to easily swap values of variables. If we wish to swap the value of two variables, we can simply destructure an array into the reversed array. We can create an array containing the variables we want to reverse and set it equal to the same array, but with the variable order changed. This will cause the references to be swapped. This is shown in the following code:

let a = 10;
let b = 5;
[ a, b ] = [ b, a ];
console.log( a ); // Expected output: 5
console.log( b ); // Expected output: 10

Snippet 1.41: Array destructuring with skipped values

Exercise 8: Array Destructuring

To extract values from an array using array destructuring assignment, perform the following steps:

  1. Create an array with three values, 1, 2, and 3, and save it into a variable called data.

  2. Destructure the array created with a single expression.

    Destructure the first array value into a variable called a. Skip the second value of the array.

    Destructure the third value into a variable called b. Attempt to destructure a fourth value into a variable called c and provide a default value of 4.

  3. Log the value of all of the variables.

Code

index.js:
const data = [ 1, 2, 3 ];
const [ a, , b, c = 4 ] = data;
console.log( a, b, c );

Snippet 1.42: Array destructuring

Outcome

Figure 1.10: Destructured variable's output

You have successfully applied an array destructuring assignment to extract values from an array.

In summary, array destructuring allows us to quickly extract values from arrays and save them into variables. Variables are assigned to array values, item by item, from left to right. If the number of variables exceeds the number of array items, then the variables are set to undefined, or the default value if specified. We can skip an array index in the destructuring by leaving a hole in the variables array. Finally, we can use destructuring assignment to quickly swap the values of two or more variables in a single line of code.

Rest and Spread Operators

ES6 also introduces two new operators for arrays called rest and spread. The rest and spread operators are both denoted with three ellipses or periods before an identifier ( ...array1 ). The rest operator is used to represent an infinite number of arguments as an array. The spread operator is used to allow an iterable object to be expanded into multiple arguments. To identify which is being used, we must look at the item that the argument is being applied to. If the operator is applied to an iterable object (array, object, and so on), then it is the spread operator. If the operator is applied to function arguments, then it is the rest operator.

Note

In JavaScript, something considered iterable if something (generally values or key/value pairs) can be stepped through one at a time. For example, an array is iterable because the items in the array can be stepped through one at a time. Objects are considered iterable because the key/value pairs can be stepped through one at a time.

The rest operator is used to represent an indefinite number of arguments as an array. When the last parameter of a function is prefixed with the three ellipses, it becomes an array. The array elements are supplied by the actual arguments that are passed into the function, excluding the arguments that already have been given a separate name in the formal declaration of the function. An example of rest destructuring is shown in the following code:

function fn( num1, num2, ...args ) {
  // Destructures an indefinite number of function parameters into the
//array args, excluding the first two arguments passed in.
  console.log( num1 );
  console.log( num2 );
  console.log( args );
}
fn( 1, 2, 3, 4, 5, 6 );
// Expected output
// 1
// 2
// [ 3, 4, 5, 6 ]

Snippet 1.43: Array destructuring with skipped values

Similar to the arguments object of a JavaScript function, the rest operator contains a list of function arguments. However, the rest operator has three distinct differences from the arguments object. As we already know, the arguments object is an array-like object that contains each argument that's passed into the function. The differences are as follows. First, the rest operator contains only the input parameters that have not been given a separate formal declaration in the function expression.

Second, the arguments object is not an instance of an Array object. The rest parameter is an instance of an array, which means that array functions like sort(), map(), and forEach() can be applied to them directly.

Lastly, the arguments object has special functionality that the rest parameter does not have. For example, the caller property exists on the arguments object.

The rest parameter can be destructured similar to how we destructure an array. Instead of putting a single variable name inside before the ellipses, we can replace it with an array of variables we want to fill. The arguments passed into the function will be destructured as expected for an array. This is shown in the following code:

function fn( ...[ n1, n2, n3 ] ) {
  // Destructures an indefinite number of function parameters into the
// array args, which is destructured into 3 variables
  console.log( n1, n2, n3 );
}

fn( 1, 2 ); // Expected output: 1, 2, undefined

Snippet 1.44: Destructured rest operator

The spread operator allows an iterable object such as an array or string to be expanded into multiple arguments (for function calls), array elements (for array literals), or key-value pairs (for object expressions). This essentially means that we can expand an array into arguments for creating another array, object, or calling a function. An example of spread syntax is shown in the following code:

function fn( n1, n2, n3 ) {
  console.log( n1, n2, n3 );
}

const values = [ 1, 2, 3 ];
fn( ...values ); // Expected output: 1, 2, 3

Snippet 1.45: Spread operator

In the preceding example, we created a simple function that takes in three inputs and logs them to the console. We created an array with three values, then called the function using the spread operator to destructure the array of values into three input parameters for the function.

The rest operator can be used in destructuring objects and arrays. When destructuring an array, if we have more array elements than variables, we can use the rest operator to capture, or catch, all of the additional array elements during destructuring. When using the rest operator, it must be the last parameter in the array destructuring or function arguments list. This is shown in the following code:

const [ n1, n2, n3, ...remaining ] = [ 1, 2, 3, 4, 5, 6 ];
console.log( n1 ); // Expected output: 1
console.log( n2 ); // Expected output: 2
console.log( n3 ); // Expected output: 3
console.log( remaining ); // Expected output: [ 4, 5, 6 ]

Snippet 1.46: Spread operator

In the preceding snippet, we destructured the first three array elements into three variables, n1, n2, and n3. We then captured the remaining array elements with the rest operator and destructured them into the variable that remained.

In summary, the rest and spread operators allow iterable entities to be expanded into many arguments. They are denoted with three ellipses before the identifier name. This allows us to capture arrays of arguments in functions or unused items when destructuring entities. When we use the rest and spread operators, they must be the last arguments that are passed into the expression they are being used in.

Object Destructuring

Object destructuring is used in a very similar way to array destructuring. Object destructuring is used to extract data from an object and assign the values to new variables. In ES6, we can do this in a single JavaScript expression. To destructure an object, we surround the variables we want to destructure with curly braces ({}), and set that expression equal to the object we are destructuring. A basic example of object destructuring is shown in the following code:

const obj = { firstName: 'Bob', lastName: 'Smith' };
const { firstName, lastName } = obj;

console.log( firstName ); // Expected output: 'Bob'
console.log( lastName ); // Expected output: 'Smith'

Snippet 1.47: Object destructuring

In the preceding example, we created an object with the keys firstName and lastName. We then destructured this object into the variables firstName and lastName. Notice that the names of the variables and the object parameters match. This is shown in the following example:

Note

When doing basic object destructuring, the name of the parameter in the object and the name of the variable we are assigning it to must match. If there is no matching parameter for a variable we are trying to destructure, then the variable will be set to undefined.

const obj = { firstName: 'Bob', lastName: 'Smith' };
const { firstName, middleName } = obj;

console.log( firstName ); // Expected output: 'Bob'
console.log( middleName ); // Expected output: undefined

Snippet 1.48: Object destructuring with no defined key

As we saw, the middleName key does not exist in the object. When we try to destructure the key and save it into the variable, it is unable to find a value and the variable is set to undefined.

With advanced object destructuring syntax, we can save the key that's extracted into a variable with a different name. This is done by adding a colon and the new variable name after the key name in the destructuring notation. This is shown in the following code:

const obj = { firstName: 'Bob', lastName: 'Smith' };
const { firstName: first, lastName } = obj;

console.log( first ); // Expected output: 'Bob'
console.log( lastName ); // Expected output: 'Smith'

Snippet 1.49: Object destructuring into new variable

In the preceding example, we could clearly see that we are destructuring the firstname key from the object and saving it into the new variable, called first. The lastName key is being destructured normally and is saved into a variable called lastName.

Much like with array destructuring, we can destructure an object and provide a default value. If a default value is provided and the key we are attempting to destructure does not exist in the object, then the variable will be set to the default value instead of undefined. This is shown in the following code:

const obj = { firstName: 'Bob', lastName: 'Smith' };
const { firstName = 'Samantha', middleName = 'Chris' } = obj;

console.log( firstName ); // Expected output: 'Bob'
console.log( middleName ); // Expected output: 'Chris'

Snippet 1.50: Object destructuring with default values

In the preceding example, we set the default values for both of the variables we are trying to destructure from the object. The default value for firstName is specified, but the firstName key exists in the object. This means that the value stored in the firstName key is destructured and the default value is ignored. The middleName key does not exist in the object and we have specified a default value to use when destructuring. Instead of using the undefined value of the firstName key, the destructuring assignment sets the destructured variable to the default value of Chris.

When we are providing a default value and assigning the key to a new variable name, we must put the default value assignment after the new variable name. This is shown in the following example:

const obj = { firstName: 'Bob', lastName: 'Smith' };
const { firstName: first = 'Samantha', middleName: middle = 'Chris' } = obj;

console.log( first ); // Expected output: 'Bob'
console.log( middle); // Expected output: 'Chris'

Snippet 1.51: Object destructuring into new variables with default values

The firstName key exists. The value of obj.firstName is saved into the new variable named first. The middleName key does not exist. This means that the new variable middle is created and set to the default value of Chris.

Exercise 9: Object Destructuring

To extract data from an object by using object destructuring concepts, perform the following steps:

  1. Create an object with the fields f1, f2, and f3. Set the values to v1, v2, and v3, respectively. Save the object into the data variable.

  2. Destructure this object into variables with a single statement, as follows:

    Destructure the f1 property into a variable named f1. Destructure the f2 property into a variable named field2. Destructure the property f4 into a variable named f4 and provide a default value of v4.

  3. Log the variables that are created.

Code

index.js:
const data = { f1: 'v1', f2: '2', f3: 'v3' };
const { f1, f2: field2, f4 = 'v4' } = data;
console.log( f1, field2, f4 );

Snippet 1.52: Object destructuring

Outcome

Figure 1.11: Created variable's output

You have successfully applied object destructuring concepts to extract data from an object.

JavaScript requires special syntax if we declare the variables before the object destructuring expression. We must surround the entire object destructuring expression with parentheses. This syntax is not required for array destructuring. This is shown in the following code:

const obj = { firstName: 'Bob', lastName: 'Smith' };
let firstName, lastName;

( { firstName: first, lastName } = obj );
// Note parentheses around expression

console.log( firstName ); // Expected output: 'Bob'
console.log( lastName ); // Expected output: 'Smith'

Snippet 1.53: Object destructuring into predefined variables

Note

Make sure that object destructuring done in this way is preceded by a semicolon on the same or previous line. This prevents the JavaScript interpreter from interpreting the parentheses as a function call.

The rest operator can also be used to destructure objects. Since object keys are iterable, we can use the rest operator to catch the remaining keys that were uncaught in the original destructuring expression. This is done similar to arrays. We destructure the keys that we want to capture, and then we can add the rest operator to a variable and catch the remaining key/value pairs that have not been destructured out of the object. This is shown in the following example:

const obj = { firstName: 'Bob', middleName: 'Chris', lastName: 'Smith' };
const { firstName, ...otherNames } = obj;
console.log( firstName ); // Expected output: 'Bob'
console.log( otherNames );
// Expected output: { middleName: 'Chris', lastName: 'Smith' }

Snippet 1.54: Object destructuring with the rest operator

In summary, object destructuring allows us to quickly extract values from objects and save them into variables. The key name must match the variable name in simple object destructuring, however we can use more advanced syntax to save the key's value into a new object. If a key is not defined in the object, then the variable will be set to false, that is, unless we provide it with a default value. We can save this into predefined variables, but we must surround the destructuring expression with parentheses. Finally, the rest operator can be used to capture the remaining key value pairs and save them in a new object.

Object and array destructuring support nesting. Nesting destructuring can be a little confusing, but it is a powerful tool because it allows us to condense several lines of destructuring code into a single line.

Exercise 10: Nested Destructuring

To destructure values from an array that's nested inside an object using the concept of nested destructuring, perform the following steps:

  1. Create an object with a property, arr, that is, set to an array containing the values 1, 2, and 3. Save the object into the data variable.

  2. Destructure the second value of the array into a variable by doing the following:

    Destructure the arr property from the object and save it into a new variable called v2, which is the array. Replace v2 with array destructuring.

    In the array destructuring, skip the first element. Save the second element into a variable called v2.

  3. Log the variable.

Code

index.js:
const data = { arr: [ 1, 2, 3 ] };
const { arr: [ , v2 ] } = data;
console.log( v2 ); 

Snippet 1.55: Nested array and object destructuring

Outcome

Figure 1.12: Nested destructuring output

You have successfully destructured values from an array inside an object.

In summary, object and array destructuring was introduced into ES6 to cut down code and allow for the quick creation of variables from objects and arrays. Array destructuring is denoted by setting an array of variables equal to an array of items. Object destructuring is denoted by setting an object of variables equal to an object of key value pairs. Destructuring statements can be nested for even greater effect.

Exercise 11: Implementing Destructuring

You have registered for university courses and need to buy the texts required for the classes. You are building a program to scrape data from the book list and obtain the ISBN numbers for each text book that's required. Use object and array nested destructuring to obtain the ISBN value of the first text of the first book in the courses array. The courses array follows the following format:

[
 {
   title: 'Linear Algebra II',
   description: 'Advanced linear algebra.',
   texts: [ {
     author: 'James Smith',
     price: 120,
     ISBN: '912-6-44-578441-0'
   } ]
 },
 { ... },
 { ... }
]

Snippet 1.56: Course array format

To obtain data from complicated array and object nesting by using nested destructuring, perform the following steps:

  1. Save the provided data structure into the courseCatalogMetadata variable.

  2. Destructure the first array element into a variable called course:

    [ course ] = [ … ]
  3. Replace the course variable with object destructuring to save the texts field into a variable called textbooks:

    [ { texts: textbooks} ] = [ … ]
  4. Replace the textbooks variable with array destructuring to get the first element of the texts array and save it into the variable called textbook:

    [ { texts: [ textbook ] } ] = [ … ]
  5. Replace the textbook variable with object destructuring to get the ISBN field and save it into the ISBN variable:

    [ { texts: [ { ISBN } ] } ] = [ … ]
  6. Log the value of the ISBN.

Code

index.js:
const courseCatalogMetadata = [
 {
   title: 'Linear Algebra II',
   description: 'Advanced linear algebra.',
   texts: [ {
     author: 'James Smith',
     price: 120,
     ISBN: '912-6-44-578441-0'
   } ]
 }
];
const [ course ] = courseCatalogMetadata;
const [ { texts: textbooks } ] = courseCatalogMetadata;
const [ { texts: [ textbook ] } ] = courseCatalogMetadata;
const [ { texts: [ { ISBN } ] } ] = courseCatalogMetadata;

console.log( course );
console.log( textbooks );
console.log( textbook );
console.log( ISBN );

Snippet 1.57: Implementing destructuring into code

Outcome

Figure 1.13: Array destructuring output

You have successfully obtained data from arrays and objects using destructuring and nested destructuring.

In this section, we discussed destructuring assignment for arrays and objects. We demonstrated how array and object destructuring simplifies code and allows us to quickly extract values from objects and arrays. Destructuring assignment allows us to unpack values from objects and arrays, provide default values, and rename object properties as variables when destructuring. We also introduced two new operators— the rest and spread operators. The rest operator was used to represent an indefinite number of arguments as an array. The spread operator was used to break an iterable object into multiple arguments.