3 hours total
40 min - Slideshow
10 min - Video
30 min - Lesson Text
40 min - Independent Practice
- JavaScript 1 - Variables, Strings, Numbers Lesson
- JavaScript 2 - Arrays, Functions Lesson
- JavaScript 3 - Conditionals, Comparisons, Booleans Lesson
- JavaScript 4 - Loops Lesson
- JavaScript 5 - Switch Statements Lesson
- JavaScript 6 - Object Literals Lesson
- Principles of object-oriented programming (aka "OOP") help developers to organize their code. It's really a way of thinking, about how code and data can interact, and how to write code to make it more reusable. It allows developers to organize code to put similar objects under the same category (called "classes"), in order to share code and logic. It also allows other parts of the code to know how to utilize objects, by knowing what's available from that class.
Virtually all companies will use object-oriented programming in their code. For example, when you do a Yelp search for "burgers", each area on the screen that describes each recommended restaurant is an instance of a class. The information is probably abstracted into a class of "Restaurant", of which "Marlowe" and "Super Duper Burgers" are instances of that class.
Participants will be able to:
- Understand some of the main concepts of object-oriented programming
- Start developing an understanding of objects and classes
- Classes and objects
- OOP (Object-Oriented Programming)
- Inheritance
- Polymorphism
- Overriding
Read through the OOP slideshow, then watch this OOP video before moving onto the lesson with code examples below. There are a lot of new concepts, so don't forget to take breaks!
If you recall from JavaScript 6 - Object Literals-
An object is a thing that has properties. This sounds simple, but it's actually very abstract! To help flesh this out, think of an example software application that keeps track of books, such as for a library. In this application, a book can be thought of as an object that has certain properties like title and author.
For example:
const book = {
"id": "827392838",
"authorFirstName": "Jane",
"authorLastName": "Doe",
"title": "The Wonderful World of JavaScript",
};
In the same example software application, we might also want to keep track of people who will borrow library books:
const borrower = {
"id": "9002",
"firstName": "Syma",
"middleInitial": "N",
"lastName": "Tec",
"phoneNumber": "(415) 123-1234",
};
Properties on an object are sometimes also referred to as "key-value pairs." Every property has a name (aka the key), "firstName", and a value, "Jane".
You can think of a class as a blueprint, or template, for creating an object. In the example above, the book
object could be created from this class:
class Book {
constructor(id, title, authorFirstName, authorLastName) {
this.id = id;
this.title = title;
this.authorFirstName = authorFirstName;
this.authorLastName = authorLastName;
}
summary() {
return this.title + " (" + this.authorLastName + ", " + this.authorFirstName + ")";
}
}
And the Borrower class could look like:
class Borrower {
constructor(id, firstName, middleInitial, lastName, phoneNumber) {
this.id = id;
this.firstName = firstName;
this.middleInitial = middleInitial;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
}
}
A class is the set of properties that describe an item, properties that make sense belonging all together in a single object. In the example above, all the information about a book is contained in the book
object and all the information about a borrower is contained in the borrower
object. Properties about a book are not stored inside a borrower
object, and vice versa.
Methods (aka functions) can be part of objects too, you can think of them as functions that are attached (or bound) to the object. For example, a method called summary()
can return a one-line summary about a book. Or, a method called checkOut()
would make sense on a borrower
object. It would not be relevant for a borrower
object to have a method summary()
, because the borrower
object does not contain any text that needs to be summarized.
This process of deciding what properties and methods belong to a particular class is sometimes called "abstraction" and is all part of the object-oriented programming experience. It is very similar to the thought process by which we decide the columns that go into a database table. A table called books
would probably have very similar columns as the properties found in the Book
class.
When you instantiate an object from a class, using the "new" keyword, that means you are creating a new object, using the class as a template, like this:
let myBook = new Book(55234, "Principles of OO Design", "Barbara", "Liskov");
Notice the function named "constructor" in the class definition, above? That is the function which is called when you use the "new" keyword with a class name. The values that you pass in parentheses become available as the named parameters list in the constructor function (id, title, authorFirstName, authorLastName).
After instantiating the Book as myBook, you can inspect it from the console-
> console.log(myBook.authorLastName);
'Liskov'
this
is a special keyword in JavaScript which refers to the current object instance. When you create an object with the new
keyword, the code in your methods (functions) can refer to properties on the current instance using this
. In the example above, in the Book
class, the constructor
method uses this
to refer to the object being constructed (via new
) and the summary
method refers to the object that the code is currently working with.
Why this matters: you may have an many Book
objects, and want to print the summary for each one-
> const books = [];
> books[0] = new Book(12345, "Why Didn't They Ask Evans?", "Agatha", "Christie");
> books[1] = new Book(12346, "The Long Goodbye", "Raymond", "Chandler");
> books[2] = new Book(12347, "Decline and Fall", "Evelyn", "Waugh");
Now, test the result-
> console.log(books[0].summary());
'Why Didn\'t They Ask Evans? (Christie, Agatha)'
> for(let i=0; i<3; i++) {
... console.log(books[i].summary());
... }
Because the books
array contains three Book
definitions, and we asked to refer to just one (books[0]
), inside the .summary()
method the this
keyword "knows" to point to the correct instance of the Book
class.
Encapsulation is the practice of keeping all of the things necessary to interact with a class in a single place. For instance, by providing a set of methods on an object, you are exposing the actions that somebody might take on it. This will make it easier for others to use your code and give you the ability to prevent others from misuse.
Encapsulation means that all the actions that we might take out on an object exist in one place. For instance, if a Borrower
wanted to check out a book, we could use a checkOut
method on that object to perform the action. In this example, each Borrower has an array of books they have checked out, and the checkOut
function adds a new book to that list. As a user of this class, all we need to know is that this method takes a book and then the method will perform the necessary actions to check out our book. The actual implementation details are "encapsulated" within the Borrower class.
class Borrower {
constructor(id, firstName, middleInitial, lastName, phoneNumber) {
this.id = id;
this.firstName = firstName;
this.middleInitial = middleInitial;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
this.libraryBooks = []; // list of books checked out from library
}
checkOut(book) {
this.libraryBooks.push(book);
}
}
As stated above, because the checkOut()
method is encapsulated in the borrower class, the rest of the program can just call the checkOut()
method and know it will perform the necessary actions to check out our book without needing to know anything about the function.
let MaryCrowley = new Borrower("1234567", "Mary", "E", "Crowley", "(555)123-4567");
MaryCrowley.checkOut(myBook);
Any new instance of the borrower class will have a checkOut()
method, too. Since checkOut()
has been abstracted away from the rest of the program by encapsulating it in the Borrower class, just calling MaryCrowley.checkOut(myBook)
will take care of it all using the specific details of that Borrower.
Another example is an iPhone. There's a whole lot going on inside, but all of the complexity of the actions is abstracted away so all the user has to understand is how to use the buttons and touchscreen interface to, say, call an openTheApp() method.
Inheritance is a way for objects to inherit properties and methods from other objects.
For example, let's say that our library doesn't just have books, but they also have audiobooks on CD to loan out. It's possible we can create an AudioBook
class, and this will have properties like title
, authorFirstName
and authorLastName
just like the Book
class:
class AudioBook {
constructor(id, title, authorFirstName, authorLastName) {
this.id = id;
this.title = title;
this.authorFirstName = authorFirstName;
this.authorLastName = authorLastName;
this.lengthInMinutes = 90;
}
}
You'll see that the AudioBook
class is exactly like the Book
class but with a lengthInMinutes
property added. Rather than constructing it this way, we can reuse code in the Book
class instead by making AudioBook
a child, or subclass, or Book
:
class AudioBook extends Book {
constructor(id, title, authorFirstName, authorLastName, lengthInMinutes) {
super(id, title, authorFirstName, authorLastName);
this.lengthInMinutes = 90;
}
}
let myAudioBook = new AudioBook(55234, "Principles of OO Design", "Barbara", "Liskov", 206);
The relationship between AudioBook
as the parent class and Book
as the child class comprises a class hierarchy. A class hierarchy can go on for multiple levels of parents and children, and parents can have many children. For example the library might have e-books--an ElectronicBook
class which is also a child of Book
, making it a sibling of AudioBook
.
And what if our library has special items, like telescopes, to lend out? We might want to add a parent above Book
, called LoanItem
. Telescope
would be a subclass of LoanItem
, at the same level as Book
.
The entire class hierarchy would then look like this:
LoanItem
/\
/ \
/ \
/ \
Telescope Book
/\
/ \
/ \
/ \
AudioBook ElectronicBook
Overriding happens when a subclass changes a value or behavior inherited from its parent class. For example, let's say that in our library application, every object that can be borrowed has a renewalLimit
property, which is the maximum amount of times one borrower can renew a loan.
The Book
class might look like this:
class Book {
constructor(id, title, authorFirstName, authorLastName) {
this.id = id;
this.title = title;
this.authorFirstName = authorFirstName;
this.authorLastName = authorLastName;
this.renewalLimit = 5;
}
}
Let's say that audiobooks, however, are in high demand and in short supply. So, the library might only want to let borrowers renew an audiobook loan once. In this case, the AudioBook
class, which still extends
(inherits from) the Book
class, can look like this:
class AudioBook extends Book {
constructor(id, title, authorFirstName, authorLastName, lengthInMinutes) {
super(id, title, authorFirstName, authorLastName);
this.lengthInMinutes = 90;
this.renewalLimit = 1;
}
}
Here the AudioBook
class is overriding the renewalLimit
property that it inherited from Book
. While Book
objects have a renewalLimit
value of 5, AudioBook
objects have a renewalLimit
of just 1.
Polymorphism is the ability to get different behaviors from objects even when the same method is being called. When we use polymorphism, we may not even know the subclass of the object we are accessing. This is because of inheritance: an object created from a subclass will have all the properties and methods inherited from any parent class in the hierarchy. And as long as we use a property or method that exists in a parent class, we don't need to actually know the subclass of the object.
To explore this, let's say that our Borrower
class has a method called checkOut()
. It takes a single argument, which is an object of type Book
or any subclass of Book
. The purpose of this method is to check out a book from the library and return the due date for returning the book to the library.
class Borrower {
constructor(id, firstName, middleInitial, lastName, phoneNumber) {
this.id = id;
this.firstName = firstName;
this.middleInitial = middleInitial;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
this.libraryBooks = []; // list of books checked out from library
}
checkOut(book) {
this.libraryBooks.push(book);
const borrowedDate = new Date();
const dueDate = book.calculateDueDate(borrowedDate);
return dueDate;
}
}
Notice that the dueDate
variable is populated by a method called calculateDueDate()
which belongs to the book
object. The Book
class and its extension, AudioBook
, both implement this method. But let's say the calculation is different, because a book can be lent out for 3 weeks while an audiobook can be lent out for 2 weeks.
class Book {
...
calculateDueDate(borrowedDate) {
return borrowedDate.setDate(borrowedDate.getDate() + 21); // 21 days is 3 weeks
}
}
class AudioBook extends Book {
...
calculateDueDate(borrowedDate) {
return borrowedDate.setDate(borrowedDate.getDate() + 14); // 14 days is 2 weeks
}
}
In this case, when the checkout
function is called, the checkout
function has no idea what subclass is being passed in. It could be an object from the Book
class, or it could be an object from the AudioBook
class. Yet it knows that whatever object is passed in has a calculateDueDate()
method, because all subclasses of Book
will either inherit that method as is, or override it, as in the case of AudioBook
.
The checkout
function is using the calculateDueDate()
method polymorphically. It's the same method name, regardless of the subclass. But the behavior differs: in the case of a Book
object, the value returned will be 3 weeks from borrowedDate
; in the case of an AudioBook
object, the value return will be 2 weeks from borrowedDate
.
There are multiple ways to create objects in JavaScript. As you do different tutorials, you might come across different syntax for creating objects. The good thing is that the concepts used are the same no matter which syntax you use to define the object.
Read this article to learn about 4 different ways to define an object in JS: https://dzone.com/articles/easy-javascript-part-13-four-ways-to-create-object
- Work through this Introduction to Classes on CodeAcademy.
Copy the Book
, AudioBook
, and Borrower
class code from the lesson above into a file on your computer. Write code to do the following:
- Add a function on
Borrower
calledgetLastBook()
that returns the last book they borrowed - Create at least 2 instances of
Borrower
, 2 instances of regularBook
, and 2 instances ofAudioBook
- Make one borrower check out 3 books
- Log the Books the person borrowed
- Log the last Book the person borrowed
For an extra challenge, add the following functions to the Borrower
class and test them:
favoriteAuthor()
returns the author that the Borrower has borrowed the most books fromreturnBook(bookId)
removes the book with the given ID from the Borrower's list of books
- Explain your completed Book and Borrower code to a partner.
There are some more advanced concepts related to OOP you can learn. Find definition of following terms on your own:
- Namespace
- Hoisting
- Arity or function.prototype.length
- Anonymous
- Closure
- Currying
- Pragma