Skip to main content

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 Web 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 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

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 easy 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

Another way to create objects is to use a constructor. A constructor is a function that contains instructions for establishing the properties of an object when that object is 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 exampleBike 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 using 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 case you still end up with a new object created in memory, with the variable bike as a pointer to that object. 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

At the time of the writing of this module, there are seven primitive types in JavaScript: string, number, Boolean, null, undefined, symbol, and bigint. Primitive types are immutable. 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.  

Note

It is highly likely new primitive types may be added in the future. As the JavaScript community identifies new needs and use cases, proposals are made to extend the language, which, if adopted, will lead to fundamental changes like this.

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 with dot notation. For instance, in the previous example, we reference the object’s 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 call it.

Let's see this at work by reimagining 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 these to our bike object, and 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 objects 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 Bikeconstructor. 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. 

You can also implement multilevel inheritance with prototypes. It is referred to as a 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

While used by Lightning Web Components, 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. We strongly recommend taking the other module in this trail, Modern JavaScript Development, which has an entire unit on Classes

While we won’t delve into too many details of JavaScript Classes here, for academic purposes it is good to see a version of the bike object implemented with class syntax. 

class Bike {
    constructor(gears, startGear){
        this.gears = gears;
        this.currentGear = startGear;
    }
    changeGear(direction, changeBy) {
        if (direction === 'up') {
            this.currentGear += changeBy;
        } else {
            this.currentGear -= changeBy;
        }
    }
}
const bike = new Bike(10, 5);
console.log(bike.currentGear); // 5
bike.changeGear('up', 2);
console.log(bike.currentGear); // 7

As you can see, the syntax looks a lot more like a class in Java or Apex. A clear difference is that the constructor function is always named constructor. An important feature is that functions and attributes automatically belong to the prototype chain without having to directly reference Object.prototype. This also simplifies creating multilevel prototype inheritance. 

Lightning Web Components and Objects

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

Classes and Lightning Web Components

Lightning Web components take advantage of many of the modern improvements to JavaScript, most notably, the use of class syntax. Components are typically defined by a JavaScript class that extends another class called LightningElement. Here’s what it looks like: 

import { LightningElement } from lwc;
export default class MyComponent extends LightningElement {
    myProperty;
    myFunction() {
        console.log(this.myProperty);
    }
} 

The functionality of a Lightning web component is defined in a JavaScript class. This example also uses some syntax we haven’t addressed yet regarding modules (import and export).

On Object Literals

In some examples in this module, for the purposes of learning how objects work, we declare functions inside object literals. Note that this is not a recommended practice in modern JavaScript. Object literals are a great way to create ad hoc data structures to pass data between functional parts of a JavaScript program, but you should avoid defining a function in an object literal. 

Resources

Comparta sus comentarios sobre Trailhead en la Ayuda de Salesforce.

Nos encantaría conocer su experiencia con Trailhead. Ahora puede acceder al nuevo formulario de comentarios cuando quiera desde el sitio de la Ayuda de Salesforce.

Más información Continuar para compartir comentarios