This chapter and the next will give you an overall taste of Object-Oriented Programming in JavaScript. This book will cover all these topics.
Object-Oriented Programming, often referred to as OOP, is a programming paradigm created to deal with the growing complexity of large software systems. Programmers discovered early on that as applications grew in complexity and size, they became difficult to maintain. One small change at any point in the program could trigger a ripple effect of errors due to dependencies elsewhere.
Programmers needed a way to create containers for data that could be changed and manipulated without affecting the entire program. They also needed a way to section off areas of code that performed specific procedures so that their programs could become the interaction of many small parts rather than one massive blob of dependency.
Enter OOP. Let's first introduce some basic concepts and terminology.
Objects form the basis for JavaScript's type system. Other than a handful of primitive types, such as numbers, strings, and booleans, almost all types in JavaScript are based on objects. Even the primitive types can be represented as objects, though this is generally invisible to the programmer.
While objects represent individual things - my cat Cheddar, your red convertible, Chris's basketball - it's helpful to think of these objects as having a type. Cheddar is a specific type of cat, your convertible is a particular type of automobile, and the basketball is a specific type of sports equipment. You can have multiple instances of any of these types. Each instance is an object with all the data and behaviors of the given type. For example, we can have multiple cats, automobiles, or items of sports equipment.
We will usually use the term "type" when referring to a primitive value or object; we'll use "class" when referring to an object whose type was defined with the class
keyword, which we'll encounter later. Outside of Launch School, you will likely see much variation. JavaScript programmers often don't make huge distinctions about type vs. class.
Objects are also called instances or instance objects. They are often created by a constructor function, a concept we'll explore in detail later. For now, here's an example of creating two instance objects with a constructor function:
function Cat(name) {
this.name = name;
}
let butterscotch = new Cat("Butterscotch");
let pudding = new Cat("Pudding");
Calling the Cat
function with the new
keyword makes the call a constructor call. We also refer to Cat
as a constructor function. The new
keyword performs several actions and returns an object whose type is the same as the function's name. Thus, butterscotch
and pudding
are instances of the Cat
type.
You can also define methods in a constructor function:
function Cat(name) {
this.name = name;
this.purr = function() {
console.log('purr');
}
this.speak = function() {
console.log('meow!');
}
}
let butterscotch = new Cat("Butterscotch");
butterscotch.purr(); // purr
butterscotch.speak(); // meow!
let pudding = new Cat("Pudding");
pudding.purr(); // purr
pudding.speak(); // meow!
Instantiation is the process of creating a new instance object. It's a fancy word for a fundamentally simple idea, but it makes you sound smart. Okay, that's not the real reason we use the term; instantiation is more specific than creation. However, in JavaScript, there is little difference between the two.
We typically use nouns when discussing types and objects; they represent concrete (non-abstract) things. Many, but not all, methods are named with verbs; they represent actions and behaviors. The objects we create are specific instances of the different types we define. The objects are our nouns, and we manipulate them using the verbs provided by the methods available to those objects.
In most OO languages, programmers define reusable classes as blueprints for objects. Each class describes the state and behaviors of objects of that particular class.
JavaScript, however, is not a traditional OO language. Technically, it doesn't support classes in the conventional sense. Even though it has something it calls classes, those classes are merely syntactical sugar for JavaScript's type system. JavaScript classes are relatively new, having been introduced to JavaScript in ECMAScript 2015 (a.k.a. ES6).
We will refer to true classes as true classes, conventional classes, or traditional classes to distinguish them from JavaScript's syntactic classes. The three terms are synonymous.
Talking about classes in JavaScript can lead to ambiguity. For instance, we can define types in various ways, only one of which uses the class
syntax. We will try to reserve the term class when talking about types defined by the class
syntax. We'll use the term types instead of classes when speaking more generally.
As mentioned earlier, traditional classes are blueprints for objects. With JavaScript, the non-primitive types are similar: they are blueprints for objects.
In our Introduction to Programming with JavaScript book, we introduced you to creating objects in JavaScript. At its most basic, you can create objects using object literals. For instance, here's how we create a student
object with two properties: a name
property with a value of Joanna
, and an age
property of 27
:
let student = {
name: 'Joanna',
age: 27,
};
This code creates a simple data structure to store some data about a student. However, OOP is about combining data and behavior into an object. Here, we only have data. If we want to also provide some behavior for the student, we need to create some methods in the object. Methods are functions carried around by objects. When called, methods access and manipulate the associated data. For instance:
let student = {
name: 'Joanna',
age: 27,
//highlight
study: function() {
console.log(`${student.name} is studying`);
},
pass: function() {
console.log(`${student.name} has passed this course`)
},
//endhighlight
};
//highlight
student.study(); // Joanna is studying
student.pass(); // Joanna has passed this course
//endhighlight
This code bundles the data and operations related to a student into an object. The study
and pass
properties define two methods: one prints Joanna is studying
and the other prints Joanna has passed this course
.
The object's structure is essentially the same as the objects we've encountered so far. The chief difference is that some of the property values are functions. That shouldn't be surprising; we've seen before that JavaScript functions are first-class values, meaning we can treat them like any other JavaScript value. That includes using them as object property values. When object properties have function values, we call them methods.
The use of student.name
in the functions is required to access the value of the name
property. We would also need student.age
to access the age
property. We'll see a better way to deal with this issue soon.
One advantage of this approach is clear: if we want to operate on the student, we don't have to search for the function and data we need. We can see at a glance what you can do with the student merely by looking at the object.
We also use dot notation to call a method. For instance:
student.study();
Using methods is so common that there's a shorthand syntax called the concise (or compact) method syntax for it:
let student = {
name: 'Joanna',
age: 27,
//highlight
study() {
//endhighlight
console.log(`${student.name} is studying`);
},
//highlight
pass() {
//endhighlight
console.log(`${student.name} has passed this course`)
},
};
student.study(); // Joanna is studying
student.pass(); // Joanna has passed this course
You can omit the :
and the function
keyword and use parentheses to denote a method.
this
KeywordThus far, in our example, we refer to the object from inside the methods by directly using the variable name, student
. This could be better. If we have multiple students with names like student1
, student2
, etc., we must change each method to use a different name. We need some way to refer to the object that contains a method from other methods in that object. The keyword this
provides this functionality:
let student = {
name: 'Joanna',
age: 27,
study() {
//highlight
console.log(`${this.name} is studying`);
//endhighlight
},
pass() {
//highlight
console.log(`${this.name} has passed this course`)
//endhighlight
},
};
student.study(); // Joanna is studying
student.pass(); // Joanna has passed this course
Thus, if we want a different student, we can copy and paste everything:
//highlight
let anotherStudent = {
name: 'Kim',
age: 30,
//endhighlight
study() {
console.log(`${this.name} is studying`);
},
pass() {
console.log(`${this.name} has passed this course`)
},
};
//highlight
anotherStudent.study(); // Kim is studying
anotherStudent.pass(); // Kim has passed this course
//endhighlight
The workings of this
are one of the most difficult JavaScript concepts to grasp; it's the source of a great deal of confusion. We'll discuss it later in this book and even more extensively later in Core. For now, you can assume that when you use this
inside a method, it refers to the object that contains the method.
As we mentioned earlier, we use types to create objects. We typically focus on state and behavior when defining a type. State refers to the data associated with an individual object, which is tracked by instance properties, i.e., properties that belong to an instance object. Behavior encompasses the actions that an object can perform.
For example, let's create some objects to represent cats. We won't follow this pattern too often, but we're not yet ready to look at any of the standard object creation patterns:
let Cat = {
purr() {
console.log("Purr...");
},
eat() {
console.log("I am eating...");
},
};
let cheddar = Object.assign({}, Cat);
let cocoa = Object.assign({}, Cat);
cheddar.name = 'Cheddar';
cheddar.color = 'orange';
cheddar.gender = 'male';
cocoa.name = 'Cocoa';
cocoa.color = 'black';
cocoa.gender = 'female';
In this snippet, we've started by creating an object named Cat
that defines a new object type in the program: a Cat
type. The Cat
type has two methods, purr
and eat
.
After defining the Cat
type, we create two objects using the Cat
object to provide their methods. We use Object.assign
in this code to create the new objects. Object.assign({}, Cat)
copies all of the methods and properties defined by the Cat
object to an empty object ({}
), then returns the resulting object. Thus, cheddar
and cocoa
acquire purr
and eat
instance methods from the Cat
object.
Finally, we define the states for the cheddar
and cocoa
objects by assigning values to the name
, color
, and gender
instance properties of cheddar
and cocoa
.
Even though cheddar
and cocoa
reference different objects, both contain identical behaviors as defined by the purr
and eat
instance methods. Both cats can purr and eat. We've described these behaviors as instance methods in the Cat
object. Such instance methods are available to all objects (or instances) created by that type.
In summary, instance properties track state, and instance methods expose behavior for objects.
In this chapter, we've learned about objects and types in JavaScript. We've also discussed instantiation, behavior, and state. Now, it's time to learn the basic concepts behind object-oriented programming.
Create an object that represents a Cessna 152 aircraft. The aircraft should include information that shows its fuel capacity of 24.5 gallons and a cruising speed of 111 knots. The aircraft should be able to take off and land.
Identify the state and behavior items in this object.
let cessna152 = {
fuelCapacityInGallons: 24.5,
cruisingSpeedInKnots: 111,
takeOff() {
console.log("Taking off!");
},
land() {
console.log("Landing!");
},
};
cessna152.takeOff(); // Taking off!
cessna152.land(); // Landing
Note that we've used the concise method syntax above for the takeOff
and land
methods. We could also use the traditional method syntax:
let cessna152 = {
fuelCapacityInGallons: 24.5,
cruisingSpeedInKnots: 111,
//highlight
takeOff: function() {
//endhighlight
console.log("Taking off!");
},
//highlight
land: function() {
//endhighlight
console.log("Landing!");
},
};
The state includes the fuelCapacityInGallons
and cruisingSpeedInKnots
properties, while the behavior includes the takeOff
and land
methods.
Write a simple constructor function that creates objects that represent books. Each book should have a title, author, and the year published. Create objects that represent the following books:
Title | Author | Year Published |
---|---|---|
Neuromancer | William Gibson | 1984 |
Doomsday Book | Connie Willis | 1992 |
When you are done, identify the type of the objects created, the constructor function, and the instance objects.
function Book(title, author, published) {
this.title = title;
this.author = author;
this.published = published;
}
let neuromancer = new Book('Neuromancer', 'William Gibson', 1984);
let doomsday = new Book('Doomsday Book', 'Connie Willis', 1992);
The object type is Book
, the constructor is the Book
function, and the instance objects are the objects assigned to neuromancer
and doomsday
.
Write a simple constructor function that creates objects that represent musical albums. Each album should have a title, artist, and release year. Create objects that represent the following 2 albums:
Title | Artist | Release Year |
---|---|---|
Thriller | Michael Jackson | 1982 |
The Dark Side of the Moon | Pink Floyd | 1973 |
When you are done, identify the type of the objects created, the constructor function, and the instance objects.
function Album(title, artist, releaseYear) {
this.title = title;
this.artist = artist;
this.releaseYear = releaseYear;
}
let thriller = new Album('Thriller', 'Michael Jackson', 1982);
let darkSide = new Album('The Dark Side of the Moon', 'Pink Floyd', 1973);
Write a constructor function that creates objects that represent smartphones. Each smartphones should have a brand, model, and release year. Add methods to check the battery level and to display the smartphones's information. Create objects that represent the following two smartphones:
Brand | Model | Release Year |
---|---|---|
Apple | iPhone 12 | 2020 |
Samsung | Galaxy S21 | 2021 |
function Smartphone(brand, model, releaseYear) {
this.brand = brand;
this.model = model;
this.releaseYear = releaseYear;
this.checkBatteryLevel = function() {
return `${this.brand} ${this.model} has 75% battery remaining.`;
};
this.displayInfo = function() {
return `${this.releaseYear} ${this.brand} ${this.model}`;
};
}
let iphone12 = new Smartphone('Apple', 'iPhone 12', 2020);
let galaxyS21 = new Smartphone('Samsung', 'Galaxy S21', 2021);
console.log(iphone12.checkBatteryLevel());
// Apple iPhone 12 has 75% battery remaining.
console.log(iphone12.displayInfo());
// 2020 Apple iPhone 12
console.log(galaxyS21.checkBatteryLevel());
// Samsung Galaxy S21 has 75% battery remaining.
console.log(galaxyS21.displayInfo());
// 2021 Samsung Galaxy S21