Objects

Object Oriented Programming is a programming paradigm that centers around modeling problems as objects that have behavior (they perform actions) and state (they have characteristics that distinguish between different objects). JavaScript objects support this paradigm, but we won't need it in this book. For now, we'll discuss objects as complex data structures, similar to arrays.

What are Objects?

Objects store a collection of key-value pairs: each item in the collection has a name that we call the key and an associated value. Contrast this with arrays, which associate values with ordered indexes. Other languages have similar key-value data structures, but they may use different names like dictionaries, associative arrays, maps, and hashes. Some developers may even use these terms regarding JavaScript objects, but it's better to use the correct name: objects.

An object's keys are strings or symbols, but the values can be any type, including other objects. We can create an object using object literal syntax:

let person = {
  name:    'Jane',
  age:     37,
  hobbies: ['photography', 'genealogy'],
};

You can also write that on a single line, which is handy in node:

> let person = { name: 'Jane', age: 37, hobbies: ['photography', 'genealogy'] }

This code shows an object named person that has 3 key-value pairs:

  • The name of the person, a string, defined by the name key.
  • The age of the person, a number, defined by the age key.
  • A list of the person's hobbies, an array of strings, defined by the hobbies key.

Braces ({}) delimit the list of key-value pairs contained by the object. Each key-value pair ends with a comma (,), and each pair has a key, a colon (:), and a value. The comma that follows the last pair is optional. Though the keys are strings, we typically omit the quotes when the key consists entirely of alphanumeric characters and underscores. The values of each pair can be any type.

We can access a specific value in an object in two ways: 1) dot notation and 2) bracket notation.

> person.name                 // dot notation
= 'Jane'

> person['age']               // bracket notation
= 37

With dot notation, we place a dot (.) and a key name after the variable that references the object. With bracket notation, we write the key as a quoted string and put it inside square brackets. Most developers prefer dot notation when they can use it. However, if you have a variable that contains a key's name, you must use bracket notation:

> let key = 'name'
> person[key]

Let's add some more key-value pairs to the person object:

> person.height = '5 ft'
= '5 ft'

> person['gender'] = 'female'
= 'female'

> person
= { name: 'Jane', age: 37, hobbies: ['photography', 'genealogy'], height: '5 ft', gender: 'female' }

In this example, we use both dot notation and bracket notation to add two new key-value pairs to the person object.

If you want to remove something from an existing object, you can use the delete keyword:

> delete person.age
= true

> delete person['gender']
= true

> delete person['hobbies']
= true

> person
= { name: 'Jane', height: '5 ft' }

delete removes the key-value pair from the object and returns true unless it cannot delete the property (for instance, if the property is non-configurable).

Key-value pairs are also called object properties in JavaScript. We can also use "property" to refer to the key name; the meaning is typically clear from context. For instance, we can talk about the name property for the person object without mentioning the value.

If a variable declared with const is initialized with an object, you can't change what object that variable refers to. You can, however, modify that object's properties and property values:

> const MyObj = { foo: "bar", qux: "xyz" }
> MyObj.qux = "hey there"
> MyObj.pi = 3.1415
> MyObj
= { foo: 'bar', qux: 'hey there', pi: 3.1415 }

> MyObj = {} // Uncaught TypeError: Assignment to constant variable.

As with arrays, this behavior can be confusing, and it occurs because of the same "variables are pointers" concept that we'll discuss in the next chapter. Essentially, a const declaration prohibits changing what thing the const points to, but it does not prohibit changing the content of that thing. Thus, we can change a property in a const object, but we can't change which object the const points to.

You can use Object.freeze with objects to freeze the property values of an object, just like you can with arrays:

> const MyObj = Object.freeze({ foo: "bar", qux: "xyz" })
> MyObj.qux = "hey there"
> MyObj
= { foo: 'bar', qux: 'xyz' }

As with arrays, Object.freeze only works one level deep in the object. If your object contains nested arrays or other objects, the values inside them can still be changed unless they are also frozen.

Objects vs. Primitives

You may remember that JavaScript has two categories of data types: primitives and objects. The primitive types are strings, numbers, booleans, null, undefined, bigints, and symbols. Primitive types are the simplest, most basic types in JavaScript.

Objects include, but aren't limited to, the following types:

  • Simple Objects
  • Arrays
  • Dates
  • Functions

We learned about simple objects in the previous section; they're structures that contain multiple named values. Arrays are also objects, but they use integer indexes instead of keys. We learn about Date and Function objects in the Core Curriculum.

Objects are complex values composed of primitive values or other objects. For example, an array object (remember: arrays are objects) has a length property that contains a number: a primitive value. Objects are usually (but not always) mutable: you can add, remove, and change their various component values.

Primitive values are always immutable; they don't have parts that one can change. Such values are said to be atomic; they're indivisible. If a variable contains a primitive value, all you can do to that variable is use it in an expression or reassign it: give it an entirely new value. All operations on primitive values evaluate as new values. Even something like 0 + 0 evaluates to a new value of 0.

> let number = 20
> let newNumber = number + 1
> newNumber
= 21

> number
= 20

> let object = { a: 1, b: 2, c: 3 }
> object.c = object.c + 1
= 4

> object
= { a: 1, b: 2, c: 4 }

The above example illustrates the difference between an immutable primitive value and a mutable object. The + operation on line 2 returns a new value (21), and assigns it to newNumber; the original value of number (20), remains unchanged. In contrast, writing a new value to the object's c property on line 10 changes the object's value. Note, however, that the c property has an entirely new number in it, precisely like what happened on line 2.

Functions Are Objects

One of the most essential concepts in JavaScript is that functions are not just code. They are, in fact, objects. This means that functions can be assigned to variables, passed to other functions as arguments, and returned by other functions. This is a remarkably handy feature. It helps make JavaScript the powerful and useful language it is despite its many faults.

Let's take a look at some examples of functions as objects. First, we can assign a function to a variable:

function hello() {
  console.log("Hello there!");
}

hello();            // Prints "Hello there!"

let greet = hello;  // `greet` now points to the `hello` function
greet();            // Prints "Hello there!"

Note that both variables, hello and greet, reference the same function object: the function we declared on lines 1-3. The hello variable is created by the function declaration and is automatic. As with any other object in JavaScript, assigning it to another variable makes both variables point to the same object.

At first, this doesn't seem like it will be useful. However, it's this same principle that lets us write function expressions instead of function declarations:

let hello = function() {
  console.log("Hello there!");
}

hello();            // Prints "Hello there!"

This feature also lets us define methods as object methods. For instance, consider JavaScript's built-in Array.prototype.push method, which we met here. It might be defined something like this (don't worry about understanding this code):

Array.prototype.push = function(newValue) {
  this[this.length] = newValue;
}

let array = [1, 2, 3];
array.push(4);

Don't worry about what this means in the above code snippet (and the next one as well). You'll learn more than you ever wanted to know about this in a future course. You won't need to understand it until then.

More importantly, this feature lets us pass functions around as arguments and return values. For instance, consider Array.prototype.forEach, which we met here. The forEach method takes a callback function as an argument; it then calls that function once for each element in the array. It might be implemented something like this:

Array.prototype.forEach = function(callback) {
  for (let index = 0; index < this.length; index += 1) {
    callback(this[index]);
  }
}

let array = [1, 2, 3];
array.forEach(function callback(value) { console.log(value) })

In the same way, we can define functions that return other functions:

function greeter(greeting) {
  return function(name) {
    return console.log(`${greeting} ${name}`);
  }
}

let hello = greeter('Hello');
let hi = greeter('Hi');

console.log(hello('Trevor'));  // prints "Hello Trevor"
console.log(hello('Ginni'));   // prints "Hello Ginni"
console.log(hi('Spencer'));    // prints "Hi Spencer"
console.log(hi('Grace'));      // prints "Hi Grace"

This last example uses a closure that lets the returned function remember the value of greeting after greeter returns. We'll discuss closure in a lot of depth later in the curriculum. It's another essential concept in JavaScript (and in many other languages). For now, though, we're only using it to demonstrate the usefulness of functions that return functions.

The fact that functions are objects is a compelling and versatile feature of JavaScript. You will see it a lot more often as you move through the JavaScript portions of the curriculum. The takeaway here is that functions are objects, not just code. If you want to assign or pass a function to something, you can. It may take some practice, but getting comfortable with this concept will open up new worlds.

What Things Aren't Objects or Primitives?

Objects and primitive values are the data and functions that you use in your program. Anything that isn't data or a function is neither a primitive value nor an object. That includes:

  • variables and other identifiers such as function names
  • statements such as if, return, try, while, and break
  • keywords such as new, function, let, const, and class
  • comments
  • anything else that is neither data nor a function

Prototypes

An interesting and handy feature of JavaScript objects is that they can inherit from other objects. When an object bar inherits from object foo, we say that foo is the prototype of bar. The practical implication is that bar now has access to properties defined on foo even though it doesn't define those properties itself.

Object prototypes and inheritance have a great deal of relevance in Object Oriented Programming (OOP). We discuss these concepts here since it is relevant to our discussion of iterating over object properties in the next section. All you need to know right now is that inheritance lets one object use the properties defined by another object and that prototypes implement inheritance in JavaScript.

The static method Object.create provides a simple way to create a new object that inherits from an existing object:

let bob = { name: 'Bob', age: 22 };
let studentBob = Object.create(bob);
studentBob.year = 'Senior';

console.log(studentBob.name); // => 'Bob'

Object.create creates a new object and sets the prototype for that object to the object passed in as an argument. Our example creates a new object named studentBob that uses bob as its prototype. That is, it creates an inheritance relationship from studentBob, the child object, to bob, the parent object.

Since studentBob inherits from bob, we can use the name property even though studentBob doesn't explicitly define it. Object.create is one way to use inheritance in JavaScript. We learn more about it and other techniques in our JavaScript courses.

Iteration

Since most objects have multiple properties, you may want to iterate over an object's keys, values, or both. There are several ways to perform these operations in JavaScript.

The for/in loop

The for/in loop behaves similarly to an ordinary for loop. The syntax and semantics are easier to understand since you don't need an initializer, ending condition, or increment clause. Instead, the loop iterates over all the keys in the object. In each iteration, it assigns the key to a variable which you then use to access the object's values. As always, seeing a concept in action is helpful:

let person = {
  name: 'Bob',
  age: 30,
  height: '6 ft',
};

for (let prop in person) {
  console.log(person[prop]);
}                             // => Bob
                              //    30
                              //    6 ft

In the above example, we iterate over the person object using the for/in loop. Line 7 declares a variable prop which, in each iteration, receives a key from the object until the object runs out of key-value pairs. We use prop inside the loop body to access and log the corresponding value.

Note that we use bracket notation within the loop. We can't use dot notation here since prop is a variable that contains a property name. The name prop is not the actual property name. That distinction is subtle, so stop a moment to think about it.

For instance, in the second iteration of the loop, prop will be set to age. If we try to use person.prop, though, it will evaluate to undefined since prop is not one of the property names in the person object. We actually want person.age, but we can't use that since we'll want a different property name during each iteration. When we write person[prop], prop gets evaluated as a variable. Thus, person[prop] gets evaluated as person['age'], and that returns the value of the desired property.

One feature -- or downside, depending on how you look at it -- of for/in is that it iterates over the properties of an object's prototypes as well:

let obj1 = { a: 1, b: 2 };
let obj2 = Object.create(obj1);
obj2.c = 3;
obj2.d = 4;

for (let prop in obj2) {
  console.log(obj2[prop]);
}         // => 3
          //    4
          //    1
          //    2

The first two items output by the above code are the "own properties" of obj2, and those are followed by the properties of the prototype object (obj1).

This behavior is undesirable when you want to limit iteration to an object's own properties, i.e., properties it defined for itself, not properties it inherited. We can use the hasOwnProperty method to get around that problem. It takes the name of a property and returns true if it is the name of one of the calling object's own properties, false if it is not.

let obj1 = { a: 1, b: 2 };
let obj2 = Object.create(obj1);
obj2.c = 3;
obj2.d = 4;

for (let prop in obj2) {
  if (obj2.hasOwnProperty(prop)) {
    console.log(obj2[prop]);
  }
} // => 3
  //    4

Object.keys

The Object.keys static method returns an object's keys as an array. You can iterate over that array using any technique that works for arrays. For instance:

let person = {
  name: 'Bob',
  age: 30,
  height: '6 ft',
};

let personKeys = Object.keys(person);
console.log(personKeys);          // => ['name', 'age', 'height']
personKeys.forEach(key => {
  console.log(person[key]);
});                               // => Bob
                                  //    30
                                  //    6 ft

Note that Object.keys returns the object's own keys: it does not include any keys from the prototype objects.

Order of Object Properties

Older versions of the ECMAScript standard (prior to ES6) don't guarantee the iteration order for an object's property keys. Many JavaScript engines took advantage of this non-guarantee. In older versions of JavaScript, you can't rely on any particular iteration order. Even in the same engine, you might get different results in separate runs of a program.

Modern versions of the standard (ES6+) guarantee the iteration order for an object's property keys. For the most part, you can assume that the iteration order is the order in which the keys get added to the object. However, be careful: if you have any symbol keys (we don't talk about symbol keys in this book) or keys that look like non-negative integers ('0', '1', '2', etc.), JavaScript will group the non-negative integers first followed by other string-valued keys, and, finally, the symbolic keys. For clarity, you should only rely on the iteration order when you know that all of the keys will be alphabetic.

Common Operations

Unlike JavaScript arrays (which, you may remember, are objects), most JavaScript objects don't have an abundance of methods that you can apply in your day to day usage. Most operations on objects involve iterating over the properties or their values. More often than not, you'll reach for methods that extract the keys or values of an object and then iterate over the resulting array. We saw an example of that when we used Object.keys to extract an object's keys as an array and then iterated over the array.

Let's take a look at some other useful methods:

Object.values

This static method extracts the values from every own property in an object to an array:

let person = { name: 'Bob', age: 30, height: '6ft' };
let personValues = Object.values(person);
console.log(personValues); // => [ 'Bob', 30, '6ft' ]

Be careful: remember that you can't predict the order of the values in the returned array.

Object.entries

While Object.keys and Object.values return the keys and values of an object, respectively, the Object.entries static method returns an array of nested arrays. Each nested array has two elements: one of the object's keys and its corresponding value:

let person = { name: 'Bob', age: 30, height: '6ft' };
console.log(Object.entries(person)); // => [[ 'name', 'Bob' ], [ 'age', 30 ], [ 'height', '6ft' ]]

Object.assign

You may sometimes want to merge two or more objects, i.e., combine the key-value pairs into a single object. The Object.assign static method provides this functionality:

> let objA = { a: 'foo' }
= undefined

> let objB = { b: 'bar' }
= undefined

> Object.assign(objA, objB)
= { a: 'foo', b: 'bar' }

Object.assign mutates the first object. In the above example, the properties from the objB object get added to the objA object, altering objA permanently in the process:

> objA
= { a: 'foo', b: 'bar' }

> objB
= { b: 'bar' }

Note that objB isn't mutated. If you need to create a new object, use an empty object as Object.assign's first argument. Note that Object.assign can take more than two arguments:

> objA = { a: 'foo' }
= undefined

> objB = { b: 'bar' }
= undefined

> Object.assign({}, objA, objB)
= { a: 'foo', b: 'bar' }

> objA
= { a: 'foo' }

> objB
= { b: 'bar' }

This code mutates neither objA nor objB and returns an entirely new object.

Objects vs. Arrays

This chapter and the last discussed two fundamental data structures: objects and arrays. It can seem overwhelming when you look at all of the different ways to represent data with code. Don't feel daunted, however. You'll use objects and arrays often. The more you use them, the more you'll learn about them. It's impossible to learn everything in the beginning, so put some effort into learning the basics, then build from there.

When you need to choose between an object or an array to store some data, ask yourself a few questions:

  • Do the individual values have names or labels? If yes, use an object. If the data doesn't have a natural label, an array should suffice.

  • Does order matter? If yes, use an array.

  • Do I need a stack or queue structure? Arrays are good at mimicking simple "last-in-first-out" stacks and "first-in-first-out" queues.

As you grow as a developer, your familiarity with these data structures may affect which one you choose to solve a problem. Practice and experiment with each to find out which data structure works best in which situations.

Summary

You now have a good start at understanding the remarkable features that objects provide. Key-value pairs are a fundamental concept that comes up often in other technologies, so it's good to understand it as well as you can manage.

Exercises

  1. Given the following code, how can you access the name of the person?

    let person = {
      name:       'Bob',
      occupation: 'web developer',
      hobbies:    'painting',
    };
    

    Solution

    person.name;
    
    person['name'];
    

    Video Walkthrough

    Please register to play this video

  2. Which of the following values are valid keys for an object?

    • 1
    • '1'
    • undefined
    • 'hello world'
    • true
    • 'true'

    Solution

    All the listed values are valid keys. Note, though, that JavaScript coerces the non-string key values to strings. Given the listed values, 1 and '1' represent the same key, as do true and 'true'. This equivalency will bite you at some point; it's inevitable, so be ready.

    > let myObj = {}
    > myObj[true] = 'hello'
    > myObj['true'] = 'world'
    > myObj[true]
    = 'world'
    

    Video Walkthrough

    Please register to play this video

  3. Use object literal syntax (e.g., { key: value, ... } notation) to create an object that behaves as an array in a for statement. The object should contain at least 3 elements. You should place your code between the braces in the let statement:

    let myArray = {
    };
    
    for (let i = 0; i < myArray.length; i += 1) {
      console.log(myArray[i]);
    }
    

    Arrays use positive integers starting with 0 as indexes. An array also must have a length property.

    Solution

    let myArray = {
      #highlight
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3,
      #endhighlight
    };
    
    for (let i = 0; i < myArray.length; i += 1) {
      console.log(myArray[i]);
    }
    

    Our array-like object isn't a perfect mimic of a regular JavaScript array, however. In particular, it doesn't modify the length property when you add or delete elements. It also doesn't support methods like forEach, filter, and push.

    Video Walkthrough

    Please register to play this video

  4. Create an array from the keys of the object obj below, with all of the keys converted to uppercase. Your implementation must not mutate obj.

    let obj = {
      b: 2,
      a: 1,
      c: 3,
    };
    

    The order of the array does not matter.

    Solution

    let objKeys = Object.keys(obj);
    let upperKeys = objKeys.map((key) => key.toUpperCase());
    console.log(upperKeys); // => [ 'B', 'A', 'C' ]
    console.log(obj); // => { b: 2, a: 1, c: 3 }
    

    The challenge of this exercise is to figure out how to iterate through the properties of obj. We showed two ways in this chapter: for/in with hasOwnProperty() and Object.keys(). The former involves a bit more work, so we use Object.keys() combined with map() to extract and uppercase the keys into an array.

    We can also use forEach, though it requires a bit more effort:

    let upperKeys = [];
    let objKeys = Object.keys(obj);
    objKeys.forEach(function(key) {
      upperKeys.push(key.toUpperCase());
    });
    console.log(upperKeys); // => [ 'B', 'A', 'C' ]
    

    Video Walkthrough

    Please register to play this video

  5. Create a new object named myObj that uses myProtoObj as its prototype.

    let myProtoObj = {
      foo: 1,
      bar: 2,
    };
    

    Solution

    let myObj = Object.create(myProtoObj);
    

    Video Walkthrough

    Please register to play this video

  6. Which of the following values are primitive values? Which are objects? Which are neither?

    • "foo"
    • 3.1415
    • [ 'a', 'b', 'c' ]
    • false
    • foo
    • function bar() { return "bar"; }
    • undefined
    • { a: 1, b: 2 }

    Solution

    Primitive Values

    • "foo"
    • 3.1415
    • false
    • undefined

    Strings, numbers, booleans, and undefined are all primitive values (as are null, bigints, and symbols).

    Objects

    • [ 'a', 'b', 'c' ] (arrays are objects)
    • function bar() { return "bar"; } (functions are objects)
    • { a: 1, b: 2 }

    Neither

    • foo is an identifier. Identifiers are used to name things that have values, such as variables and functions, but they are not values by themselves. Thus, they are neither primitive values nor objects.

    Video Walkthrough

    Please register to play this video

  7. Add a qux property with value 3 to the myObj object we created in the previous exercise. Now, examine the following code snippets:

    let objKeys = Object.keys(myObj);
    objKeys.forEach(function(key) {
      console.log(key);
    });
    
    for (let key in myObj) {
      console.log(key);
    }
    

    Without running this code, can you determine whether these two snippets produce the same output? Why?

    Solution

    myObj.qux = 3;
    

    Both snippets iterate over the keys of myObj. However, for..in iterates over all of the object's keys, including those in the prototype object, myProtoObj. Thus, snippet 2 logs:

    qux
    foo
    bar
    

    Snippet 1 iterates solely over myObj's "own" properties - that is, those defined directly on the object, not its prototype. Thus, it logs:

    qux
    

    We can add a conditional to snippet 2 to get the same output from for..in: all we need to do is check whether the key is myObj's own property:

    for (let key in myObj) {
      if (myObj.hasOwnProperty(key)) {
        console.log(key);
      }
    }
    

    Video Walkthrough

    Please register to play this video

  8. Create a function that creates and returns a copy of an object. The function should take two arguments: the object to copy and an array of the keys that you want to copy. Do not mutate the original object.

    The function should let you omit the array of keys argument when calling the function. If you do omit the argument, the function should copy all of the existing keys from the object.

    Here are some examples for your reference:

    let objToCopy = {
      foo: 1,
      bar: 2,
      qux: 3,
    };
    
    let newObj = copyObj(objToCopy);
    console.log(newObj);        // => { foo: 1, bar: 2, qux: 3 }
    
    let newObj2 = copyObj(objToCopy, [ 'foo', 'qux' ]);
    console.log(newObj2);       // => { foo: 1, qux: 3 }
    
    let newObj3 = copyObj(objToCopy, [ 'bar' ]);
    console.log(newObj3);       // => { bar: 2 }
    

    Solution

    function copyObj(sourceObject, keys) {
      let destinationObject = {};
    
      if (keys) {
        keys.forEach(function(key) {
          destinationObject[key] = sourceObject[key];
        });
    
        return destinationObject;
      } else {
        return Object.assign(destinationObject, sourceObject);
      }
    }
    

    Video Walkthrough

    Please register to play this video

  9. What does the following program log to the console? Why?

    let foo = {
      a: 'hello',
      b: 'world',
    };
    
    let qux = 'hello';
    
    function bar(argument1, argument2) {
      argument1.a = 'hi';
      argument2 = 'hi';
    }
    
    bar(foo, qux);
    
    console.log(foo.a);
    console.log(qux);
    

    Solution

    This program logs 'hi' and 'hello' to the console. This is because objects are mutable and primitives are immutable.

    When bar is invoked on line 13, argument1 is initialized to an object and argument2 is initialized to a string value.

    Line 9 reassigns an object property (argument1.a), which will mutate the object. Line 10 reassigns a variable, which completely replaces the value of the local argument2 variable.

    Thus, line 9 mutates foo by reassigning its a property to a new value ('hi'), and line 10's variable reassignment doesn't effect any variables outside of bar.

    Because foo was mutated, the access to foo.a on line 15 logs 'hi'. On the other hand, the variable reassignment on line 10 does not mutate qux's value, so line 16 logs hello - the original value of the qux variable.

    Video Walkthrough

    Please register to play this video

  10. How many primitive values are there in the following expression? Identify them. How many objects are there in the expression? Identify those objects.

    [1, 2, ["a", ["b", false]], null, {}]
    

    Solution

    There are 6 primitive values and 4 objects:

    Primitive Values Objects
    1 [1, 2, ["a", ["b", false]], null, {}]
    2 ["a", ["b", false]]
    "a" ["b", false]
    "b" {}
    false
    null

    The outermost set of brackets defines an array (an object) that contains 5 elements. The elements with values 1, 2, and null are all primitive values, while ["a", ["b", false]] is a nested array, and {} is nested object. The nested array has 2 elements, one of which is a primitive value ("a"), while the other is yet another nested array. Finally, this innermost array contains two elements, "b" and false, both of which are primitive values.

    Video Walkthrough

    Please register to play this video

  11. Write some code to replace the value 6 in the following object with 606:

    let obj = {
      foo: { a: "hello", b: "world" },
      bar: ["example", "mem", null, { xyz: 6 }, 88],
      qux: [4, 8, 12]
    };
    

    You don't have to search the object. Just write an assignment that replaces the 6.

    Solution

    obj.bar[3].xyz = 606;
    
    obj["bar"][3]["xyz"] = 606;
    

    Video Walkthrough

    Please register to play this video

  12. Consider the following code:

    function foo(bar) {
      console.log(bar, bar, bar);
    }
    
    foo("hello"); // should print "hello hello hello"
    bar("hi");    // should print "hi hi hi"
    

    As written, this code will raise an error on line 6. Without creating a new function or changing lines 5 or 6, update this code to work as intended.

    Solution

    function foo(bar) {
      console.log(bar, bar, bar);
    }
    
    //highlight
    let bar = foo;
    //endhighlight
    
    foo("hello"); // prints "hello hello hello"
    bar("hi");    // prints "hi hi hi
    

    All we have to do here is create an alias for foo. We do that by simply initializing a bar variable with a reference to the foo function.

    Video Walkthrough

    Please register to play this video

  13. Consider the following code:

    function foo(bar) {
      console.log(bar());
    }
    
    foo();    // Should print 'Welcome'
    foo();    // Should print 3.1415
    foo();    // Should print [1, 2, 3]
    

    As written, this code will raise an error on line 5. Without modifying foo, update this code to print the desired text.

    Solution

    function foo(bar) {
      console.log(bar());
    }
    
    //highlight
    foo(function() { return "Welcome" });
    foo(function() { return 3.1415 });
    foo(function() { return [1, 2, 3] });
    //endhighlight
    

    Since foo expects a function argument (as indicated by the call to bar on line 2), we know we need to pass each invocation a function that returns the desired value. Thus, we define and pass functions that return "Welcome", 3.1415, and [1, 2, 3] in the three invocations.

    Video Walkthrough

    Please register to play this video

  14. Identify all of the variables, object property names, primitive values, and objects shown in the following code (assume the code has not been executed). Don't panic if you miss a few items - this exercise is more challenging than it looks.

    function hello(greeting, name) {
      return greeting + ' ' + name;
    }
    
    function xyzzy() {
      return { a: 1, b: 2, c: [3, 4, 5], d: {} };
    }
    
    const howdy = hello('Hi', 'Grace');
    let foo = xyzzy();
    

    Solution

    • Variables: hello, xyzzy, greeting, name, howdy, and foo. Remember that function names and parameters are variables. In addition, constants created with const are variables that can't be reassigned. Property names in an object are not variables.

    • Property Names: a, b, c, d. It's also worth noting that array indexes are property names, so 0, 1, and 2 are property names for the [3, 4, 5] array. (Don't worry if you missed the array indexes as property names.)

    • Primitive values: ' ', 1, 2, 3, 4, 5, 'Hi', and 'Grace' are the most obvious primitive values. It's worth noting that object property names are usually strings, and strings are primitive values. Thus, we should also include 'a', 'b', 'c', and 'd' in the list and 0, 1, and 2 for the array indexes of [3, 4, 5].

    • Objects: The functions hello and xyzzy are both objects, as are the following:

      • { a: 1, b: 2, c: [3, 4, 5], d: {} }
      • [3, 4, 5]
      • {}.

    Since we didn't run the code, 'Hi Grace' is not one of the primitive values in this code. That primitive won't be created until we run the code.

    Video Walkthrough

    Please register to play this video

  15. Identify all of the variables, object property names, primitive values, and objects in the following code. This problem is even more challenging than the previous one.

    function bar(arg1, arg2) {
      let foo = 'Hello';
      const qux = {
        abc: [1, 2, 3, [4, 5, 6]],
        def: null,
        ghi: NaN,
        jkl: foo,
        mno: arg1,
        pqr: arg2,
      };
    
      return qux;
    }
    
    let result = bar('Victor', 'Antonina');
    

    Note that some items in this program may appear in multiple categories.

    Solution

    • Variables: bar, arg1, arg2, foo, qux, and result.

    • Property Names: abc, def, ghi, jkl, mno, pqr, 0, 1, 2, and 3. Don't forget that array indexes are property names.

    • Primitive values: 'Hello', 1, 2, 3, 4, 5, 6, null, NaN, 'Victor', and 'Antonina' are the most apparent primitive values. Since property names are strings in most cases, and strings are primitive values, we should also include 'abc', 'def', 'ghi', 'jkl'. 'mno', 'pqr', '0', '1', '2', and '3'.

    • Objects: The bar function, the array [1, 2, 3, [4, 5, 6]], the array [4, 5, 6], and the object assigned to qux on line 3 are all objects.

    Video Walkthrough

    Please register to play this video