Back to Schedule

Week 4: Objects, Classes, this

Objects

An object is a collection of related data and/or functionality (which usually consists of several variables and functions — which are called properties and methods when they are inside objects.) The data we store in an object is not ordered — we can only access it by calling its associated key. You can create an object with key-value pairs using the following syntax:

var person = {
  name: ['Bob', 'Smith'],
  age: 32,
  gender: 'male',
  interests: ['music', 'skiing'],
  bio: function() {
    return `${this.name[0]} ${this.name[1]} is ${this.age} years old. He likes ${this.interests[0]} and ${this.interests[1]}.`;
  },
  greeting: function() {
    return `Hi! I'm ${this.name[0]}.`;
  }
};

You can access object properties using dot notation. Below are some examples of how we can access properties on our person object:

person.name[0]  // 'Bob'
person.age  // 32
person.interests[1]  // 'skiing'
person.bio()  // 'Bob Smith is 32 years years old. He likes music and skiing.'
person.greeting()  // 'Hi! I'm Bob.'

You can also access object properties using bracket notation. Here’s how we would access the same properties as above, this time using bracket notation:

person['name'][0]  // 'Bob'
person['age']  // 32
person['interests'][1]  // 'skiing'
person['bio']()  // 'Bob Smith is 32 years years old. He likes music and skiing.'
person['greeting']()  // 'Hi! I'm Bob.'

Dot notation has the advantage of concision. On the other hand, bracket notation is useful when you want to construct a property name dynamically:

var introduction = function(inFirstPerson) {
  var prop = inFirstPerson ? 'greeting' : 'bio';
  return person[prop]();
}

console.log(introduction(true));  // 'Hi! I'm Bob.'

You can update object property values as well as add new ones:

person.age = 29;
person['eyes'] = 'hazel';
person.farewell = function() { return 'Bye everybody!'; }

Accessing these properties then gives us:

person.age  // 29
person.eyes  // 'hazel'
person.farewell()  // 'Bye everybody!'

JavaScript objects are like map data structures in Java or C++, with the restriction that the keys are strings. When accessing object properties, the property name is automatically converted to a string. As JavaScript arrays are objects, this applies to arrays as well. Thus, we observe the following behavior:

var a = [37, 47];
a['1'] = 53;
console.log(a[1]);  // 53

Classes

A recent specification of JavaScript introduced a class syntax that looks and feels like classes in Java or C++. I only say “looks and feels” because JavaScript obeys a different object model from those languages. In fact, the JavaScript class syntax is merely syntactic sugar over its true object model. For our purposes in this course, however, we may assume that JavaScript classes behave in the same way as the classes with which we’re already familiar.

Classes act as object templates. Let’s look at a concrete example.

class Animal {
  constructor(name) {
    this.name = name;
    this.belly = [];
  }

  eat(food) {
    this.belly.push(food);
  }

  getName() {
    return this.name;
  }
}

// Dog is a subclass of Animal
class Dog extends Animal {
  constructor(name, breed) {
    // call the parent class constructor
    super(name);
    this.breed = breed;
  }

  bark() {
    console.log('Woof!');
  }
}

JavaScript class definitions look similar to how we define objects but with two main differences. First, every class definition requires a constructor method, which is called every time a new instance of the class is created. Second, there are no commas between methods in a class definition.

Once we’ve defined a class, we can create an object instance of it using the new operator.

var a = new Animal('Abu');
var d = new Dog('Buddy', 'Golden Retriever');

These objects have access to the properties and methods defined in their associated class definitions.

a.eat('banana');
console.log(a.name);  // 'Abu'
console.log(a.belly);  // ['banana']

d.eat('basketball');
console.log(d.name);  // 'Buddy'
console.log(d.belly);  // ['basketball']
d.bark();  // 'Woof!'

this

Many JavaScript programmers get confused by what gets bound to the this keyword. Part of this is because this behaves differently in JavaScript than it does in other languages like Java or C++.

Let’s consider how this works in Java.

class Vehicle {
  int maxSpeed;

  public Vehicle(int maxSpeed) {
    this.maxSpeed = maxSpeed;
  }

  public int getMaxSpeed() {
    return this.maxSpeed;
  }
}

class Car extends Vehicle {
  String make;

  public Car(int maxSpeed, String make) {
    super(maxSpeed);
    this.make = make;
  }

  public String getMake() {
    return this.make;
  }
}

In Java, we can always assume that this will be bound to an instance of the containing class or a subclass thereof. This is because Java methods are always associated with a calling object. For example, consider how we can invoke the method getMaxSpeed defined in the Vehicle class. We can call v.getMaxSpeed(), where v is an instance of Vehicle or Car, but we cannot invoke getMaxSpeed() (from outside of the class definition) without an object calling the method.

Things get more complicated in JavaScript, where functions are first-class objects. Unlike Java methods, which are always associated with some class definition, JavaScript functions can be passed around freely like numbers and booleans can. For example, returning to our Animal/Dog example from above, we can do the following:

var d = new Dog('Snowball');
var bark = d.bark;
bark();  // 'Woof!'

We run into trouble, however, when we try to do the same thing with a method that has this in its definition:

var a = new Animal('Joe');
var getName = a.getName;
getName();  // Uncaught TypeError: Cannot read property 'name' of undefined

The error message here is saying that, when running the getName function, this is bound to undefined rather than a as we might expect.

To resolve this, we can explicitly bind the value of this to the desired object:

var a = new Animal('Joe');
var getName = a.getName.bind(a);
getName();  // 'Joe'

How to determine what is bound to this

Now that we’ve seen how this can behave differently in JavaScript than we might expect, let’s see how we can predict its behavior.

What is bound to this in a function body is determined by the function call-site: the location in the code where the function is called (not where it’s declared). Once we have identified the function call-site, there are four rules we can use to determine what is bound to this.

  1. If the function is called when instantiating a class using the new operator (i.e., the function is the constructor method of the class), then this is bound to the newly instantiated object.
     class Foo {
       constructor() { this.bar = 'baz'; }
     }
    
     var foo = new Foo();  // <-- at this call-site, JavaScript runs the constructor function and binds `this` to the new object
    
  2. If the function is an explicitly bound function, then this is bound to the specified object.
     class Foo {
       constructor(bar) { this.bar = bar; }
       getBar() { return this.bar; }
     }
    
     var foo = new Foo('baz');
     var explicitlyBoundFunction = foo.getBar.bind({ bar: 'not baz'});
     console.log(explicitlyBoundFunction());  // 'not baz'
    
  3. If the function is called as the method of an object, then this is bound to that object.
     var foo = {
       bar: 'baz',
       getBar() { return this.bar; }
     }
    
     console.log(foo.getBar());  // 'baz'
    
  4. Otherwise, this is undefined.

this in the context of arrow functions

The four rules above apply to this in a regular function definition. JavaScript also comes with another class of functions called arrow functions.

var square = (x) => x * x;
console.log(square(4));  // 16

Arrow functions have their own rule for binding this. Whereas regular functions bind this according to their call-sites, arrow functions bind this according to their declarations. Specifically, they bind this to whatever is bound to this at the site of their declaration.

var foo = {
  bar: 'baz',
  getRegularFunction() {
    return function() {
      return this.baz;
    }
  },
  getArrowFunction() {
    return () => this.baz;
  }
};

var regularFunction = foo.getRegularFunction();
var arrowFunction = foo.getArrowFunction();

console.log(regularFunction());  // undefined because none of the first three
                                 // rules for regular functions apply at this call-site

console.log(arrowFunction());  // 'baz' because foo.getArrowFunction() binds 
                               // `this` to `foo` in the method body of getArrowFunction
                               // and the returned arrow function binds `this` to
                               // whatever is `this` in its surrounding context

Arrow functions are often useful for fixing this in the definition of callback functions.

var foo = {
  bar: 'baz',
  setUpEventHandler() {
    $('button').click(() => { console.log(this.bar); })
  }
}

foo.setUpEventHandler();

In the above program, the last line binds this in the body of setUpEventHandler to foo. By then passing an arrow function into the click method, we ensure that the value logged upon clicking a button is 'baz'. If we had passed a regular function into the click method instead, it is not clear what value would be logged because it would depend on where exactly jQuery invokes the click handler in its internal implementation.