- Explain why we need classes
- Create a class
- Add methods and properties to a class
- What is
this
? Why do we need it?
Sometimes, we need to create new objects with the same attributes repetitively. Imagine we want to make a bunch of pet profiles. Each user profile would need at least the following.
- name
- type
- age
- greeting
- take for a walk
{
name: "Buttons",
type : "miniature horse",
age: 10,
greeting: "sniff sniff",
walk: "clip clop, clip clop",
}
Great! One object. How can we create another one?
- Copy-pasting and then changing the details?
- Typing it all from scratch?
Both methods above can introduce typing errors and aren't very controlled.
There is a better way, with classes. You've been using classes all along in this course, like the Math
methods, these are JavaScript's version of classes, but we have not spent much time making our own.
We will start the class name with a capital letter whenever we create a class. This is convention. In JavaScript, you could keep it all lowercase, and it would not throw an error. In other languages, capitalization may be enforced.
class Animal {}
Now we can instantiate
(make a new instance) / create a new object using this class.
const buttons = new Animal();
const marshmallow = new Animal();
console.log(buttons);
console.log(marshmallow);
Right now, our objects are empty and don't do anything. Let's add a method. It is very maintainable and efficient that we will add our code in one place, but it will update both objects. This keeps our code DRY (principle of don't repeat yourself).
class Animal {
walk() {
console.log("Clip clop, clip clop");
}
}
const buttons = new Animal();
const marshmallow = new Animal();
console.log(buttons);
console.log(marshmallow);
buttons.walk();
marshmallow.walk();
If we create a second method, we don't add a comma
between them. We can also pass in an argument to our function.
class Animal {
walk() {
console.log("Clip clop, clip clop");
}
greet(otherBeing) {
console.log(`Sniff sniff, ${otherBeing}`);
}
}
const buttons = new Animal();
const marshmallow = new Animal();
console.log(buttons);
console.log(marshmallow);
buttons.walk();
marshmallow.walk();
buttons.greet("Fluffy");
marshmallow.greet("Patches");
Right now, our class has methods (functions that are attached to the class), but objects also have properties. Let's add some properties.
We add properties by using a special function called constructor
.
class Animal {
constructor() {
this.type = "miniature horse";
this.age = 4;
this.color = "roan";
this.isFriendly = true;
}
walk() {
console.log("Clip clop, clip clop");
}
greet(otherBeing) {
console.log(`Sniff sniff, ${otherBeing}`);
}
}
const buttons = new Animal();
const marshmallow = new Animal();
console.log(buttons);
console.log(marshmallow);
Try misspelling constructor
. What happens?
this
is another keyword. Let's go back to the vending machine problem.
-
a vending machine is an object
-
it has an array of snacks (make 3 snacks)
-
snacks are objects that have a name and a price
-
a vending machine has a function vend that allows the user to enter the array position (a number) of the snack, and then that snack will be returned
-
Be able to call vendingMachine.vend() with a valid integer to return a snack
const vendingMachine = {
snacks: [
{
name: "kitkat",
price: 6,
},
{
name: "sun chips",
price: 7,
},
{
name: "apple",
price: 12,
},
],
vend(input) {
console.log("vending", vendingMachine.snacks[input]);
},
};
vendingMachine.vend(1);
This strategy worked fine because we knew the object's name: vendingMachine
.
However, with classes, we are using classes to make blueprints of objects - we can create many objects, all with different names.
So we need a way to say this
object's snacks, this
object's age property. We need a pronoun
- a generic term to refer to the item.
JavaScript uses the keyword this
.
const vendingMachine = {
snacks: [
{
name: "kitkat",
price: 6,
},
{
name: "sun chips",
price: 7,
},
{
name: "apple",
price: 12,
},
],
vend(input) {
console.log("vending", this.snacks[input]);
},
};
vendingMachine.vend(1);
When we make a new class and add properties inside the constructor, we use this
to be sure we are referencing the properties inside this
class.
Our world is very boring; all we have are roan-colored miniature horses. We need a way to customize each object to have more animals.
class Animal {
constructor(name, type, color, walkStyle) {
this.name = name;
this.type = type;
this.age = 4;
this.color = color;
this.isFriendly = true;
this.walkStyle = walkStyle;
}
walk() {
console.log(this.walkStyle);
}
greet(otherBeing) {
console.log(`Sniff sniff, ${otherBeing}`);
}
}
const buttons = new Animal("Buttons", "turtle", "green");
const fluffy = new Animal("Fluffy", "cat", "calico", "Strut, strut");
const marshmallow = new Animal(
"Marshmallow",
"miniature horse",
"white",
"Clip clop, clip clop"
);
console.log(buttons);
console.log(fluffy);
console.log(marshmallow);
Notice what happens when we don't pass enough arguments into our functions in JavaScript.
It can be confusing to understand how the values are being set. Let's go over it.
-
First, we are creating a new animal and passing in the name
Buttons
. -
This goes up into the
Animal
class and is passed in as the argumentname
inside the constructor -
This value is then on the right side in
this.name = name;
- Which sets the property
this.name
toButtons
.
Try drawing it with an annotation tool on your computer or writing it out on paper. Naming this variable the same is very useful once you are comfortable with the concept. Still, it can be challenging for a learner to understand which value represents what and how they all tie together.
You can also try naming it like so:
constructor (parameter1, parameter2, parameter3) {
}
And then playing around with the syntax/values until it makes sense.
In this course, it can feel like everything is very fast-paced. One of the hardest things can be to slow down and give yourself the time to fully explore a fundamental concept to become faster with it in the future.
Sometimes you want to create default values that can be overwritten. For example, if a user created a profile but did not include a picture, you would likely want a default placeholder image set that can be updated later.
Let's look at the code to write a default value.
class Animal {
constructor(name, type, color, walkStyle, isFriendly = true) {
this.name = name;
this.type = type;
this.age = 4;
this.color = color;
this.isFriendly = isFriendly;
this.walkStyle = walkStyle || "Walka, walka";
}
walk() {
console.log(this.walkStyle);
}
greet(otherBeing) {
console.log(`Sniff sniff, ${otherBeing}`);
}
}
const buttons = new Animal("Buttons", "turtle", "green");
const fluffy = new Animal("Fluffy", "cat", "calico", "Strut, strut", false);
const marshmallow = new Animal(
"Marshmallow",
"miniature horse",
"white",
"Clip clop, clip clop"
);
console.log(buttons);
console.log(fluffy);
console.log(marshmallow);
Thought question, do walkStyle
and isFriendly
need to come last as parameters? Or could they be first?
We can alter the properties of an instance after it is created.
marshmallow.color = "dappled grey";
console.log(marshmallow);
JavaScript, being JavaScript, lets you do this, for better or worse. Other languages, by default, will prevent you from overwriting the initial value, and you would have to write specific code to do so.
It is better to practice naming a value you would not change (often referred to as private) starting with an _ and to write functions to deal with changes.
Let's take a look:
class Animal {
constructor(name, type, color, walkStyle, isFriendly = true) {
this.name = name;
this._type = type;
this._age = 4;
this.color = color;
this.isFriendly = isFriendly;
this.walkStyle = walkStyle || "Walka, walka";
}
walk() {
console.log(this.walkStyle);
}
greet(otherBeing) {
console.log(`Sniff sniff, ${otherBeing}`);
}
ageUp() {
this._age++;
}
}
const buttons = new Animal("Buttons", "turtle", "green");
const fluffy = new Animal("Fluffy", "cat", "calico", "Strut, strut", false);
const marshmallow = new Animal("Marshmallow", "miniature horse", "white");
buttons.ageUp();
console.log(buttons);
console.log(buttons._type);
Here, we are controlling the aging process by only increasing 1. With type, we should not be able to change Buttons from a turtle to a bird. Again, in other languages, we would be prevented from changing type. Still, with JavaScript, we can start the variable name with an underscore _
to make this code more readable for other coders and to know that this value should not be overwritten.
We can pass objects into other objects to have them interact.
class Animal {
constructor(name, type, color, walkStyle, isFriendly = true) {
this.name = name;
this._type = type;
this._age = 4;
this.color = color;
this.isFriendly = isFriendly;
this.walkStyle = walkStyle || "Walka, walka";
}
walk() {
console.log("Clip clop, clip clop");
}
greet(otherBeing) {
console.log(`Sniff sniff, ${otherBeing}`);
}
classyGreeting(otherClassyBeing) {
console.log(`Howdy ${otherClassyBeing.name}`);
}
ageUp() {
this._age++;
}
}
const buttons = new Animal("Buttons", "turtle", "green");
const fluffy = new Animal("Fluffy", "cat", "calico", "Strut, strut", false);
const marshmallow = new Animal("Marshmallow", "miniature horse", "white");
buttons.classyGreeting(marshmallow);
marshmallow.classyGreeting(buttons);
As we've mentioned, you have already been working with JavaScript's version of classes with things like Math
methods.
You don't do this when you use a Math
method.
const mathStuff = new Math();
console.log(mathStuff.abs(-5));
Rather, we do this:
console.log(Math.abs(-5));
This makes sense because we want all the math methods to be organized so that they are all together. However, we don't want to instantiate a new class to use them.
We can then add the word static
to declare methods and properties to a class.
class StringExtra {
static reverse(str) {
return str.split("").reverse().join("");
}
static description =
"I have some extra methods for doing things with strings";
}
console.log(StringExtra.reverse("I palindrome I"));
console.log(StringExtra.description);
Eloquent JavaScript
Chapter 6: The Secret Life of Objects
Read this code, analyze what it does, then research what is prototype
is in JavaScript, how it works and what does it do?
How does it relate to this lesson in classes?
String.prototype.reverse = function () {
return this.split("").reverse().join("");
};
console.log("foMO".reverse());