- There's a lot of content and links in this piece of curriculum. Don't get carried away reading every single link. Make sure you understand what's necessary, bookmark the ones that could be helpful later (or this whole curriculum page), and move forward.
- 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.
-
JavaScript is mostly used as a functional programming language more than an object oriented language like C++ or Java. Functional programming in JS can be elegantly used to organize code. Learn more about the advantages of Functional Programming in JS.
-
JavaScript has a unique, reliable OOP style, although it is less famous. JS frameworks like React, Angular and Vue rely on OOP and have recently increased in popularity amongst developers world wide.
-
JavaScript and its frameworks have become must-have skills for any web developer to master. These skills are important for technical interviews. They also help in learning other frameworks or languages easily since many ideas are taken from JavaScript frameworks.
-
The software development lifecycle has to go through many different stages. Writing clean, organized, reusable code is essential for developers to maintain projects. Hence, the five principles of SOLID applied in JavaScript are important to master the ability to write clean and professional code. Here are a few resources on SOLID principles and the importance of OOP in JS:
SOLID - the first 5 principles of OOD with JS
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 Book
as the parent class and AudioBook
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
- Video: What is Object-Oriented Programming? (19 mins - start at 0:37 to skip intro, and 2x video speed recommended)
- What is Object-Oriented Programming?- educative
- Khan Academy: Play with Interactive Object Instances
- Reference: JS Object Basics - Mozilla Developer Network
- Chapter: Object-Oriented Programming - Eloquent JavaScript;