All About Objects

We've discussed most basic data types in Javascript, as well as ways we can manipulate them using control flow structures like if-else statements or loops. But we haven't discussed one of the most fundamental data types in Javascript: the object.

Every non-primitive in Javascript is an object. This includes arrays and functions. It doesn't include numbers, strings, booleans, or undefined types. For more information on primitives see the MDN documentation on data structures in Javascript.

Objects in Javascript are very similar to the data structures known as hashmaps or dictionaries in other programming languages.

Objects have keys and values. The key is used to access a particular value in an object in the same way as an array.

// each entry in an object is a key-value pair
let myObject = {
    "cats": "cute",
    "dogs": "cool",
}

// we access values the same way as in an array
console.log(myObject["cats"])

// we can also access them using . syntax
console.log(myObject.cats)

// we assign new keys the same way we access them
myObject["birds"] = "smelly"

// you can loop through keys in an object using for in loops
for (key in myObject) {
    console.log(`${key} are ${myObject[key]}`)
}

One of the main uses of objects is to make it easy to pass data between functions, especially when returning multiple values.

function analyseAnimals(animalData) {
    let total = 0
    let count = 0
    for (animal in animalData){
        total += animalData[animal]
        count += 1
    }
    return {
        "total": total
        "average": total / count
    }
}

const myPets = {
    "cat": 1,
    "dog": 1,
    "rabbit": 3,
    "fish": 24,
    "parrot": 1
}

const data = analyseAnimals(myPets)
console.log(`Total Pets: ${data["total"]}; Average # of each pet: ${data["average"]}`)

Within the methods of an object, this is used to refer to the object, and lets an object access its own properties. Note that this can get complicated but we cover only the usual case here.

const counter = {
    value: 0,
    reset: () => {
        this.value = 0
    },
    increment: () => {
        this.value += 1 
    },
}

console.log(counter.value)
counter.increment()
counter.increment()
console.log(counter.value)
counter.reset()
console.log(counter.value)

Objects can store any data type, just like an array. You can do some tricks with objects by exploiting this property; for example they can be used to simplify complex if-else statements:

// incomplete example of using objects to set up calculator functions

// if statement version
if (operation == '+') add(a,b)
else if (operation == '-') subtract(a,b)
else if (operation == '/') divide(a,b)

// object version
let operations = {'+': add, '-': subtract, '/': divide}
console.log(operations[operation](a,b))

One thing that's notable about objects is that they're not copied into functions (technically, they're not passed by value). Instead, they pass a reference to themselves. This means that objects passed into a function can be modified. For example:

const normalise = function (object) {
    object.name = object.name.toUpperCase()
    object.description = object.description.toUpperCase()
}

// const objects can have their values modified, but the object they reference can't be re-assigned
const myObject = {
    "name": "My cool object",
    "description": "This object is incredibly cool."
}

normalise(myObject)
console.log(myObject)

Whether or not modifying objects that are passed into functions is good practice or not is controversial and a major difference between functional and object-oriented styles of Javascript.

In the real world, this is usually determined by the problem you're solving rather than programming philosophy.

Exercises

  1. Given an array of numbers, return an object containing the frequencies of each number. For example, for the array [1,1,2,5,5], you should return {1: 2, 2: 1, 5: 2}.
  2. (Leetcode #1) Given two arrays of numbers and a target number, return the indexes of one number from each array that sum to the target as an array of two numbers. For example, given [1,2,3,4,5] and [1,2,5,7,8] with a target of 7, you should return the array [1,2], representing 2+5.
  3. setInterval(function, delay) is a built in function which lets us run a given function repeatedly. You can also use clearInterval(interval) to stop a previously-declared interval. Create a timer object with three methods: start, stop, and reset. It should also have the property time. When start is called, the timer should start counting until stop is called. When reset is called, the timer should reset to zero.

Classes and Inheritance

In addition to being able to use objects as a way to easily store a lot of data, we can also use them as objects proper, as seen in object-oriented languages like C# or Java.

Every object in Javascript has a type, as we covered in our very first class. It's possible to create our own named types with the class keyword!

// classes use TitleCase by convention
class Book {
    constructor(title, description, author) {
        this.title = title
        this.description = description
        this.author = author
    }
} 

// we call the constructor function using new CLASSNAME
const myFavorite = new Book(
    "Love in the Time of Cholera",
    "In their youth, Florentino Ariza and Fermina Daza fall passionately in love. When Fermina eventually chooses to marry a wealthy, well-born doctor, Florentino is heartbroken, but he is a romantic. As he rises in his business career he whiles away the years in 622 affairs—yet he reserves his heart for Fermina. Her husband dies at last, and Florentino purposefully attends the funeral. Fifty years, nine months, and four days after he first declared his love for Fermina, he will do so again.",
    "Gabriel Garcia Marquez"
)

// it's just a regular object
console.log(myFavorite.title)
// ...but it has the type Book
console.log(typeof myFavorite)
// by default, JS classes inherit from Object
console.log(myFavorite instanceof Object)

Pay attention to the last line of this code example. How can myFavorite be both a Book and an Object? To answer that we need to understand inheritance.

Inheritance is a feature where child objects inherit properties from their parent objects. For example, in the below diagram, there are 4 classes: Polygon, Triangle, Rectangle, and Square.

Triangle and Rectangle inherit from Polygon directly, while Square inherits indirectly, via Rectangle. This is because Square is a special case of Rectangle.

How can we represent this in Javascript? Let's add three properties to our classes: width, height, and the area() method.

class Polygon {
    constructor (width, height) {
        this.width = width
        this.height = height
    }

    area () {
        // we can't do this without a more complex representation
        return undefined
    }
}

class Triangle extends Polygon {
    constructor (width, height) {
        // super calls the parent's constructor and sets up this
        super(width, height)
    }

    area() {
        return (this.width * this.height) / 2
    }
}

class Rectangle extends Polygon {
    constructor (width, height) {
        super(width, height)
    }

    area() {
        return this.width * this.height
    }
}

class Square extends Rectangle {
    constructor (size) {
        super(size,size)
    }

    area () {
        return this.width * this.width
    }
}

// you can use your own methods to play with this example!
// try typeof, instanceof, and calling the area() function on different types of object

There are two important things to note here. First is that the constructor function in a child class will overwrite the constructor in a parent class. However, we can still access the parent's constructor via the super method, and in fact we need to call this method to get this set up correctly. super must also be called before any further modifications to this in the child class's constructor.

Exercises

  1. Add in a Shape parent class. This should be a parent of Polygon.
  2. Add in a Circle class as a child of Shape.
  3. Make the Circle class set itself up correctly. Add an area function to Circle which will calculate the area correctly as Math.pow(Math.PI * radius).

Assignment

Write a quiz game where the player has to select from 4 choices (e.g. a,b,c,d). Each question needs to store the question, the possible answers, and the correct answer as an object. Note that the easiest way to do this is to use the quick way to make arrays and objects:

let listOfQuestions = [
    {
        // question body
    }
]

At the end of the quiz, the player should be given their score and the option to play again. Please write 3 or more questions.

Hint: you probably want to write some functions for extracting information from your list of questions.

Extensions

These are optional but highly recommended if you want to reinforce your knowledge of object-oriented Javascript.

  1. Make a Question class with a constructor method, which allows us to create new questions by calling new Question().
  2. Make a Quiz class which takes a list of Question objects, adds them into a list, and keeps track of which questions have been asked. When nextQuestion() is called on the Quiz, it should supply the next question until all questions are exhausted. It should also keep track of the player's current score.
  3. Make the order of the questions in the quiz random by using Math.random().
  4. Add an additional type of Question to the quiz. This should be called OpenQuestion and allow for a free-text response. If the free text reply is correct, add a point. Rename the original Question class to MultipleChoiceQuestion and make a new Question class which is a parent of both OpenQuestion and MultipleChoiceQuestion.