Classes
Intro
In the last lecture, we learned about encapsulation - bundling data and methods that act on that data into an object. We learned about using closures to create private variables.
const makeFriendsManager = (...initialFriends) => {
const friends = [...initialFriends];
return {
getFriends() {
return [...friends];
},
addFriend(newFriend) {
if (typeof newFriend !== 'string') return;
friends.push(newFriend);
}
}
}Factory Functions Waste Memory
The nice thing about encapsulation is that we can re-use makeFriendsManager to create multiple objects that look alike: each friends manager has getFriends and addFriends methods.
This kind of function is called a factory function and each object created from this factory function is called an instance.
// factory functions return objects
const makeFriendsManager = (...initialFriends) => {
const friends = [...initialFriends];
return {
getFriends() {...},
addFriend(newFriend) { ... },
}
}
// instances of the factory function
const myFM = makeFriendsManager();
const yourFM = makeFriendsManager();The objects myFM and yourFM definitely have the same behavior. But do they share that behavior? That is, are the methods they each have referencing the same exact function?
// are these the same object?
console.log(myFM === yourFM)
// are the methods of these objects the same?
console.log(myFM.addFriend === yourFM.addFriend)Classes

A class defines a type of object and the properties/methods that those objects will share.
Class Definition and new
newMany programming languages implement classes in some manner.
In JavaScript, it starts with the class keyword, an uppercase name, and curly braces. Like this:
// class definitions
class User {
}
class Pet {
}
// creating class instances
const ben = new User();
const clifford = new Pet();
// Instances are objects derived from a particular class
console.log(ben); // User {}
console.log(clifford); // Pet {}With a class definition, we can create new instances of that class using the new keyword. An instance is an object that is derived from a class.
Note: Even though User is treated like a function (we invoke it), you must use the new keyword when making an instance (you'll get an error if you don't)
// User is a function, but you can't just call it
console.log(typeof User); // function
const ben = User(); // error: you must use the new keyword to invoke a constructor functionInstanceof
We can use the instanceof operator (kind of like the typeof operator) to see if an object is derived from the given class.
console.log(ben instanceof User); // true
console.log(ben instanceof Pet); // false
console.log(clifford instanceof User); // false
console.log(clifford instanceof Pet); // trueSetting Properties With A Constructor
Right now, the class definitions only allow us to create blank objects. But objects are only useful if they have properties.
There are two kinds of properties that instances of a class can have:
Properties with default values that all instances start with
Properties whose values are provided when the instance is made
class User {
// Default instance properties are defined here. We can change these later.
// Notice that the `this` keyword isn't used
isAdmin = false;
password = null;
// Instance properties that require inputs go in the constructor
constructor(name, email) {
this.name = name; // <-- The `this` keyword references the new instance object being created
this.email = email;
}
}
const ben = new User('ben', 'ben@mail.com');
const zo = new User('zo', 'zo@mail.com');
console.log(ben, zo);
// User {isAdmin: false, password: null, name: 'ben', email: 'ben@mail.com'}
// User {isAdmin: false, password: null, name: 'zo', email: 'zo@mail.com'}Class constructor functions have some quirks to get used to:
constructoris a special method name. You must use this name. When you create a new instance of a class usingnew, JavaScript will look to see if the class has aconstructormethod and it will execute that method.The
constructorfunction can accept parameters whose values are provided when the instance is madeThe
thiskeyword, when used in aconstructor,references the new instance object being created.
Defining Instance Methods
Remember, encapsulation wants us to bundle data with methods that operate on that data.
Adding methods to a class definition looks like this:
class User {
isAdmin = false;
password = null;
constructor(name, email) {
this.name = name;
this.email = email;
}
// notice that we don't have commas between methods
// These methods are shared by ALL instances of the class
setPassword(newPassword) {
// When used in a method, this references the object invoking the method
this.password = newPassword;
}
validatePassword(passwordToCheck) {
if (!this.password) {
console.log('No password set.');
return false;
}
if (passwordToCheck === this.password) {
console.log('It matches!');
return true;
}
console.log('Wrong password!');
return false;
}
}
const ben = new User('ben', 'ben@mail.com');
ben.validatePassword('1234'); // No password set.
ben.setPassword('1234');
ben.validatePassword('1234'); // It Matches!When used in a method, the this keyword refers to the object invoking the method.
const ben = new User('ben', 'ben@mail.com');
const zo = new User('zo', 'zo@mail.com');
// they are the same method
console.log(ben.setPassword === zo.setPassword); // true
// when we invoke the method, the value of `this` changes
ben.setPassword('1234');
zo.validatePassword('1234'); // No password set.Next time, we'll look at making the password private.
Quiz!
Can you spot the mistake(s) with the code below?
const Animal = {
this.owners = [];
constructor: (species, sound) => {
this.species = species;
this.sound = sound;
},
makeSound() {
console.log(sound)
}
}
const dog = Animal('canine', 'woof');Challenge
Create a class called FoodItem. Every instance of FoodItem should have the following properties and methods
name— the name of the itemprice- the price of the item in US dollarsweight- the weight of the itemgetPricePerPound()- returns the price / pound of the item
For example, I should be able to use this FoodItem class like so
const apple = new FoodItem('apple', 1, 0.5);
console.log(apple);
// FoodItem { name: 'apple', price: 1, weight: 0.5 }
console.log(apple.getPricePerPound());
// 2Now, create a second class called ShoppingCart. Every instance of ShoppingCart should have the following properties and methods:
items— an array that starts empty. It should holdFoodIteminstances.addItem(FoodItem)— takes in aFoodIteminstance and adds it to theitemsarray.getTotalPrice()- calculates the total price of allFoodItemsin theitemsarray
For example, I should be able to use this ShoppingCart class like so
const myCart = new ShoppingCart();
console.log(myCart); // ShoppingCart { items: [] }
myCart.addItem(new FoodItem('apple', 1, 0.5)) // name, price, weight
myCart.addItem(new FoodItem('bread', 5, 1))
myCart.addItem(new FoodItem('cheese', 7, 2))
console.log(myCart); // ShoppingCart { items: Array(3) }
console.log(myCart.getTotalPrice()); // 13Summary
A class defines a type of object with shared methods and properties
It has a constructor function for defining the default properties that every instance of that class (objects of that type) will have.
All instances of that class inherit the class' methods.
Classes are defined using the
classkeywordInstances of a class are created using the
newkeyword and the class constructor.When used in a constructor function,
thispoints to the newly created objectWhen used in a method,
thispoints to the object invoking the method
class Animal {
owners = [];
constructor (species, sound) {
this.species = species;
this.sound = sound;
}
makeSound() {
console.log(this.sound)
}
}
const dog = new Animal('canine', 'woof');
dog.makeSound(); // 'woof'
const cat = new Animal('feline', 'meow');
cat.makeSound(); // 'meow'Last updated