Some additional features of JavaScript

These notes will introduce you to a set of additional features of the JavaScript language. My motivation for introducing these features at this time is that they will play a role in the coming weeks as we work our way through the material on React. To make our journey through React a little easier I want to cover these topics in advance so that you will be familiar with them when we encounter them later.

Object reference variables

Here is a short snippet of JavaScript code:

let one = {a:12,b:"Hello"};
let two = one;
two.a = 10;
console.log(one.a);

What do you think this code will print to the console? If you said "12" you have an incorrect understanding of an important aspect of the behavior of objects in JavaScript. You may think that the assignment statement

let two = one;

makes a copy of the object stored in one and places it in two. If that is the case, the subsequent statement

two.a = 10;

would update the a property of object two and leave object one untouched. In fact, what happens here is that both variables one and two will contain a reference to the object we created in the first statement. Since both variables refer to the same object, changing the a property of either of these references will change the a property for everybody who refers to the same object.

The same principle applies to passing parameters to functions:

function setA(obj) {
  obj.a = 10;
}

let one = {a:12,b:"Hello"};
setA(one);
console.log(one.a);

This example will also print 10: when we pass the object stored in one to the setA() function we are passing a reference to the object. When the function modifies the a property of the object you passed it the reference to, you will effectively be changing the a property of the variable one, since both one and the parameter obj refer to the same object.

All of this raises an obvious question: what if you wanted to make a copy of the object that one refers to and store that copy in two? The following somewhat clunky code does that:

let one = {a:12,b:"Hello"};
let two = {a:one.a,b:one.b};
two.a = 10;
console.log(one.a);

In a later section in these notes we will see a cleaner way to do the copy.

Destructuring assignments

Consider the following bit of code:

let one = {a:12,b:"World",c:12.2,d:"Hello"};
let n = one.a;
let greeting = one.d;

In this example we are decomposing an object into its component parts and storing some of those parts in separate variables. Breaking out some or all of the properties of an object into distinct variables is a common enough action in JavaScript that the language now supports a somewhat cleaner alternative syntax for this called a destructuring assignment:

let one = {a:12,b:"World",c:12.2,d:"Hello"};
let {a,d} = one;

The second statement above introduces two new variables a and d and assigns the a and d properties of the object one to them. (Note that for this work the names of the variable have to exactly match the names of the properties.)

This idea is much more commonly applied to parameter passing. Passing two or more properties of an object to a function is a pretty common thing. Here is a first example using an older syntax for this:

function example(n,str) {
  console.log("n is " _+ n);
  console.log("str is " + str);
}
let one = {a:12,b:"World",c:12.2,d:"Hello"};
example(one.a,one.d);

Here is a more modern version of the same example using a destructuring assignment for the parameters:

function example({a,d}) {
  console.log("n is " + a);
  console.log("str is " + d);
}
let one = {a:12,b:"World",c:12.2,d:"Hello"};
example(one);

The spread/rest operator

Version 6 of JavaScript introduced a new operator called the spread operator. The purpose of the spread operator is to break the components of some thing (usually an array or an object) and break them into individual elements. The best way to demonstrate how this works is with an example.

let A = [1,2,3];
let B = [0,...A];

The ... operator takes the contents of the A array and spreads them out so they can be used to assemble a new list based on A.

The same trick can be used to spread out the properties of an object:

let A = {a:10,b:"World"};
let B = {c:"Hello",...A};

This example spreads out the properties of A and adds those spread out properties to the list of properties of a new object B.

A side effect of the way the spread operator works is that it can also be used to make copies of objects:

let A = {a:10,b:"World"};
let B = {...A};

The same ... sequence implements a second operator in JavaScript, the rest operator, which is used in destructuring assignments and parameter passing. Here is an example of the rest operator being used in a destructuring assignment:

let one = {a:12,b:"World",c:12.2,d:"Hello"};
let {a,d,...other} = one;

The assignment in the second statement above essentially says: "Put the a property of object one into the variable a, the d property into d, and then put any leftover properties from one into a new object that the variable other will refer to."

Here is an example of both a spread and a rest operator being used in a function:

function incA({a,...other}) {
  return {a:a+1,...other};
}
let one = {a:12,b:"World",c:12.2,d:"Hello"};
let two = incA(one);
console.log(two);

In this example we construct a function that expects to receive an object as its parameter. We will split off the a property of the object into the parameter a, and gather the rest of the properties of the object into a new object stored in the other parameter (which is a rest parameter). Then, in the body of the function we create and return a new object made up of a slightly modified a value and all of the properties in other by using a spread operator.

Using the array map() and filter() methods

The JavaScript array class offers a number of useful methods. This section of notes will cover two of these methods, the map() and filter() methods.

The map() method takes a function as its parameter and returns a new array whose elements are the result of applying that function to each element of an original array:

let A = [1,2,3,4];
function doubleIt(x) {
  return 2*x;
}
let B = A.map(doubleIt);

You can use an arrow function as an alternative to defining and then passing in a function.

let A = [1,2,3,4];
let B = A.map(x => 2*x);

The filter() function is like map() in that it is meant to return a new array based on an existing array. The difference with filter() is that you are expected to pass it a boolean function. The new array will consist of only those elements for which the boolean function returns true:

let A = [1,2,3,4,5,6];
function isEven(x) {
  return x % 2 == 0;
}
let B = A.filter(isEven);
// B will equal [2,4,6]

Once again, you can pass an arrow function to filter():

let A = [1,2,3,4,5,6];
let B = A.filter(x=>x%2==0);