📣 Attention Salesforce Certified Trailblazers! Link your Trailhead and Webassessor accounts and maintain your credentials by December 14th. Learn more.
close
trailhead

Work with Objects, Classes, and Prototypical Inheritance

Learning Objectives

After completing this unit, you’ll be able to:

  • Create objects using object literal notation and constructors.
  • Assign properties and functions to objects.
  • Identify the role of prototypes in JavaScript object inheritance.
  • Describe JavaScript class syntax.
  • Describe the role of inheritance and object literal notation in Lightning components.

There are plenty of ways to describe JavaScript as a language. No matter which definition you choose, everyone can agree on the importance of the JavaScript concept of an object. The more you understand JavaScript objects and how they work, the better you’ll be able to write effective JavaScript.

A few notes on objects before we get started. 

  • Objects don’t have classes the way an Apex, Java, or C# developer might think of them.
  • Each object inherits from another object.
  • Objects are mutable.
  • Objects get their own variable context when they’re created.

Creating Objects

Syntactically speaking, there are are several ways to create an object in JavaScript. But no matter how you create an object, it’s actually abstracting an underlying API called Object.create().  

In some instances there’s good reason to use Object.create() directly, but we won’t cover that here. Instead, let’s look at more common ways to create objects. 

Object Literal Notation

The first object creation syntax is referred to as object literal notation. This is a simple declarative way to declare and assign an object all at once. The object is then assigned immediately as part of the same statement. 

const bike = {
  gears: 10,
  currentGear: 3,
  changeGear: function(direction, changeBy) {
    if (direction === 'up') {
      this.currentGear += changeBy;
    } else {
      this.currentGear -= changeBy;
    }
  }
}
console.log(bike.gears); // 10
console.log(bike.currentGear); //3
bike.changeGear('up', 1);
console.log(bike.currentGear); //4

Object literal notation is declarative in nature. The object bike in this example has three members: the gears  and currentGear properties and the changeGear function. To reference those members once the object has been created, use dot notation. 

Note

Note

You may notice JSON and object literal notation resemble each other, but they’re not the same. JSON is a data-interchange format. Object literal notation is a programming syntax. However, the JSON spec is based on object-literal notation, so it’s natural to conflate the two.

Literal objects are great for one-off objects. But if you want to create two or more objects of the same type, they are not practical. For that, you need repeatable logic to create new objects. 

New Objects with Constructors

An alternative to object literal notation is the use of a constructor. A constructor is a function that contains instructions for establishing the properties of an object when it's created and assigned. This has an advantage over object literal as you can create many instances of objects that have the same properties. 

function Bike(gears, startGear) {
  this.gears = gears;
  this.currentGear = startGear;
}
Bike.prototype.changeGear = function(direction,changeBy){
  if(direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear += changeBy;
  }
}
const bike = new Bike(10, 3);
console.log(bike.gears); // 10
console.log(bike.currentGear); //3
bike.changeGear('up', 1);
console.log(bike.currentGear); //4

In this example Bike is a normal JavaScript function that defines the object. We follow JavaScript convention and capitalize the first word to signal that this function is a constructor. The new keyword is critical. Without new, the this pointer will not point to the object you expect and causes unintended behavior. We revisit this when we cover context in a later unit. 

Notice the assignment of the changeGear function is done via something called prototype. This ensures the function is defined once and shared by all instances created from this constructor. We cover use of prototypes and inheritance later in this unit. 

Syntax-wise, object literal notation and constructors are pretty different. But in each instance you still have a new object created in memory, with the variable bike being a pointer to that object. Of course with the constructor you can make lots of bike objects with the same properties and functions. 

Assigning Properties and Functions to Objects

If you inferred from the bike examples above that there are two possible types of members in an object: properties and functions, you would be correct. 

Properties come in three basic shapes.

  • Primitives
  • Objects
  • Arrays

There are six primitive types in JavaScript: string, number, Boolean, null, undefined, and symbol. When a variable is a primitive type, it’s passed by value when assigned. That is to say, each time a primitive is assigned, a copy of the value is made and assigned to the new variable. 

Pretty much anything that isn’t a primitive in JavaScript is an object. In object literal notation, object properties are denoted by curly brackets. 

Arrays themselves are also implemented as objects in JavaScript. Arrays can be created with the Array() constructor function or with literal notation denoted by square brackets. 

Functions have their own unit in this module, so we won’t talk about them here, but based on the above, let’s take another pass at defining a more complex bike object using object literal notation.

const bike = {
  frontGearIndex: 0,
  rearGearIndex: 0,
  transmission: {
    frontGearTeeth: [30,45],
    rearGearTeeth: [11,13,15,17,19,21,24,28,32,36]
  },
  calculateGearRatio: function() {
    let front = this.transmission.frontGearTeeth[this.frontGearIndex],
        rear = this.transmission.rearGearTeeth[this.rearGearIndex];
    return (front / rear);
  },
  changeGear: function(frontOrRear, newValue) {
    if (frontOrRear === 'front') {
      this.frontGearIndex = newValue;
    } else {
      this.rearGearIndex = newValue;
    }
  }
};

Referencing Properties by Bracket Syntax

Referencing an object member is most commonly done using dot notation. For instance, in the previous example, we can reference its properties and functions as follows.

bike.frontGearIndex
bike.transmission.frontGearTeeth
bike.calculateGearRatio()

In dot notation, there are strict rules for the names of properties. However, JavaScript also allows for another syntax called bracket notation. The members above would be referenced as follows in bracket notation.

bike["frontGearIndex"]
bike["transmission"]["frontGearTeeth"]
bike["calculateGearRatio"]()

While it requires more typing, bracket notation has two benefits. You can name your property or function anything you want, and because it’s a string, you can pass a property or function name through a variable and then call it.

Let's see this at work by reimagning the changeGear function. We now use four functions to define shifting the front and rear gears either up or down. In the changeGear function, we construct the name of the function to call based on String parameters and then call it. 

changeGear: function(frontOrRear, upOrDown) {
  let shiftFunction = frontOrRear + upOrDown;
  this[shiftFunction]();
},
frontUp: function(){
  this.frontGearIndex += 1;
},
frontDown: function(){
  this.frontGearIndex -= 1;
},
rearUp: function(){
  this.rearGearIndex += 1;
},
rearDown: function(){
  this.rearGearIndex -= 1;
}

Add this to our bike object, then we can see them at work. 

console.log(bike.calculateGearRatio()); // 2.727272727
//Calls the frontUp() function
bike.changeGear("front", "Up");
console.log(bike.calculateGearRatio()); // 4.090909091
//calls the rearUp() function
bike.changeGear("rear", "Up");
console.log(bike.calculateGearRatio()); // 3.461538461

Object Mutability

Apart from the different syntaxes for defining objects, there’s another key principle of JavaScript objects: mutability. 

Objects in JavaScript are mutable, which means that if you want to modify the shape of an object, you can. 

Let’s take the bike object we created. We could, for instance, add a new property or function. 

bike.isTandem = true;
bike.popAWheelie = function() {
…
};

Even though you may not have access to the code where the object is initially defined, you can modify the shape of your object once it’s in memory. The important point, though, is that only one instance of the object changes. Let’s look back at our Bike constructor:

const bike1 = new Bike();
const bike2 = new Bike();
bike1.isTandem = true;
console.log(bike1.isTandem); // true
console.log(bike2.isTandem); // undefined

If you want several instances to share the same properties or methods, then there’s an inheritance model. Let’s look at that. 

Objects and Inheritance

Despite not having classes as defined by classical languages, JavaScript still has an inheritance model, called prototype inheritance

A prototype is, in fact, another object. It sits in memory, and defines properties or functions that other objects inherit if they share the same prototype. 

Traditionally in JavaScript objects share the same prototype by sharing the same constructor function. Remember the Bike constructor. We assign the changeGear function to something called prototype

function Bike(gears, startGear) {
  this.gears = gears;
  this.currentGear = startGear;
}
Bike.prototype.changeGear = function(direction, changeBy) {
  if (direction === 'up') {
    this.currentGear += changeBy;
  } else {
    this.currentGear -= changeBy;
  }
}

This way every object created from Bike, inherits the changeGear function. 

There is also multilevel inheritance with prototypes. It is referred to as the prototype chain. Implementing a prototype chain using constructor functions is complex, and requires a fair amount of boilerplate code. It’s also beyond the scope of this module. What you need to know is that in order to address prototype chain complexity, ECMA put a standard in place for a more straightforward syntax to implement inheritance: the class syntax. 

Classes and JavaScript

If you read the word "class" and got a warm fuzzy feeling, thinking that you’d be looking at something that creates true class-based inheritance, prepare to be disappointed. Still, the class keyword in JavaScript is a nice bit of syntactic sugar to address the complexities of prototype inheritance using constructor functions. Under the covers, the engine is still using Object.create and there is still no class (in the object-oriented sense), just that in-memory prototype object that is the actual source of inheritance. 

The good news is that it does read a lot more like code from Java or C#, with a few JavaScript-specific things to take into account. 

Note

Note

Multilevel prototype chains including class syntax are beyond this module. See the resources links at the end of this unit for more details on working with newer features of JavaScript objects.

Lightning and Objects

Several parts of this unit are relevant to Lightning components, including some of the syntax we discussed, and prototype chains. 

Object Literal Notation

The Lightning Component framework makes heavy use of object literal notation in all the JavaScript artifacts surfaced to developers, specifically in the client-side controller, the helper, and the renderer. 

//CalculateGearRatioController.js
({
  handleInit : function(component, event, helper) {
    const recordId = component.get("v.recordId");
    helper.getGears(recordId, component);
  },
  handleSelectChange : function(component, event, helper) {
    helper.setGearRatio(component); 
  }
})

Component Inheritance

Every rendered component is itself a JavaScript object with actual JavaScript code in its controller. Each custom component also inherits from an aura:component prototype. You can also use the extends attribute in your custom component to create subcomponents. In this way the framework abstracts the creation of the prototype chain between components. 

<!--Component c:bike-->
<aura:component extensible="true">
  ...
</aura:component>
<!--Component c:mountainBike-->
<aura:component extends="c:bike">
  ...
</aura:component>

Resources

Code Sample Project for This Module

JavaScript Object

Working with Objects

Details of the JavaScript Object Model

Lightning Components Basics Trailhead Module

Using Inheritance in Lightning Components 

retargeting