[toc]
🏃 RUSH! Some stuff was skipped because a a lack of time. We'll go back eventually and fill in some of the blanks.
Recall that ==objects are and data structures that hold many values which consist of properties and methods.==
We often need to create many objects of the same type. To do this in an efficient way, we define a class, which allows us to set up the general structure for an object. We can then reuse that structure to buil multiple objects. These objects all have the same set of keys, but will have various values assigned to each key.
Let's revisit the animal astronauts from earlier exercises.
When we create an object to hold an astronaut's data, it might look something like:
let fox = {
name: 'Fox',
age: 7,
mass: 12,
catchPhrase: function(repeats){
let phrase = 'LaunchCode';
for (let i = 0; i < repeats; i++){
phrase += ' Rocks';
}
returns phrase;
}
}
console.log(`${fox.name} is ${fox.age} years old and has a mass of ${fox.mass} kg.`);
console.log(`${fox.name} says, "${fox.catchPhrase(3)}."`);Fox is 7 years old and has a mass of 12 kg.
Fox says, "LaunchCode Rocks Rocks Rocks."
The fox object contains all the data and functions for the astronaut named 'Fox'.
Of course, we have multiple astronauts on our team. To store data for each one, we would need to copy the structure for fox multiple times and then change the values to suit each crew member. This is inefficient and repetitive.
By letting us define our ouw classes, JavaScript provides a better way to create multiple similar objects.
classDiagram
class Astronaut {
String name
Number age
Number mass
String catchPhrase()
}
class Astronaut0 {
name: 'Fox'
age: 7,
mass: 12,
catchPhrase: 'LC Rocks!'
}
class Astronaut1 {
name: 'Tortoise'
age: 120,
mass: 227,
catchPhrase: 'Code4Ever'
}
class Astronaut2 {
name: 'Hippo'
age: 25,
mass: 1500,
catchPhrase: 'No bugs!'
}
Astronaut ..> Astronaut0
Astronaut ..> Astronaut1
Astronaut ..> Astronaut2
Just like the function keyword defines a new function, the class keyword defines a new class. By convention, a class name starts with a capital leader to distinguish them from JavaScript function and variable names. Class names are in title case (MyClassName) where as methods and properties are in camel case (myFunctionName).
Remember that ==classes are blueprints for building muiltiple objects of the same type.== The general forma for delcaring a class is:
class ClassName {
constructor(parameters){
// assign properties
}
// define methods
}Note the keyword constructor. This is a special method for creating objects of the same type, and it assigns the key-value pairs. Parameters are passed into constructor rather than the class declaration.
Let's set up an Astronaut class to help us store data about our animal crew. Each animal has a name, age, and mass, and we assign these properties in the constructor as follows:
class Astronaut {
constructor(name, age, mass){
this.name = name;
this.age = age;
this.mass = mass;
}
}The this keyword defines a key-value pair, where the text attached to this becomes the key, and the value follows the equal sign (this.key = value).
constructor uses the three this statements (this.name = name, etc.) to achieve the same results as the object declaration let objectName = {name: someName, age: someNumber, mass: someMass }. Each time the Astronaut class is called, constructor builds an object with the same set of keys, but assigns different values to the keys based on the arguments.
ℹ️ NOTE: Each class requires one
constructor. Including more than oneconstructorresults in a syntax error. Ifconstructoris left out of a class declaration, JavaScript adds an emptyconstructor(){}automatically.
To create an object from a class, we use the new keyword.
let objectName = new ClassName(arguments);new create an instance of a class, which means that the object generated shared the same set of keys as every other object made from the class. However, the values assigned to key may differ.
Example: Let's create an object for two of our crew members: Fox and Hippo.
class Astronaut { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; } } let fox = new Astronaut('Fox',7,12); let hippo = new Astronaut('Hippo',25,1500); console.log(typeof hippo, typeof fox); // "object object" console.log(hippo,fox);object object Astronaut { name: 'Hippo', age: 25, mass: 1500 } Astronaut { name: 'Fox', age: 7, mass: 12 }
In lines 9 and 10, we call the Astronaut class twice and pass in different sets of arguments, creating the fox and hippo objects.
The output of line 14 shows that fox and hippo are bot the same type of object (Astronaut). The two share the same keys, but they have different values assigned to those keys.
Note: Two object created from the same class are NOT equal, even if the keys within the objects have the same values. We discussed this previously back in Chapter 12. 🎗️
After creating an Astronaut object, we can access, modify, or add new key-value pairs as described in Chapter 12.
Try it: Play around with modifying and adding properties inside and outside of the
classdeclaration. repl.itclass Astronaut { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; } } let fox = new Astronaut('Fox', 7, 12); console.log(fox); console.log(fox.age, fox.color); fox.age = 9; fox.color = 'red'; console.log(fox); console.log(fox.age, fox.color);Astronaut { name: 'Fox', age: 7, mass: 12 } 7 undefined Astronaut { name: 'Fox', age: 9, mass: 12, color: 'red' } 9 'red'
Attempting to print fox.color in line 12 returns undefined, since that property is not included in the Astronaut class. Line 15 adds the color property to the fox object, but this change will not affect any other objects created with Astronaut.
What happens if we create a new Astronaut without passing in all of the required arguments?
Try it! repl.it
class Astronaut { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; } } let tortoise = new Astronaut('Speedy', 120); console.log(tortoise.name, tortoise.age, tortoise.mass);Speedy 120 undefined
To avoid issues with missing arguments, we can set a default value for a parameter as follows:
class Astronaut {
constructor(name, age, mass = 54){
this.name = name;
this.age = age;
this.mass = mass;
}
}Now if we call Astronaut but do not specify a mass value, the constructor automatically assigns a value of 54. If an argument is included for mass, then the default value is ignored.
The questions below refer to a class called Car.
class Car {
constructor(make, model, year, color, mpg){
this.make = make;
this.model = model;
this.year = year;
this.color = color;
this.mpg = mpg;
}
}❓ Question: If we call the class with
let myCar = new Car('Chevy', 'Astro', 1985, 'gray', 20), what is the output byconsole.log(typeof myCar.year)?a.
objectb.stringc.functiond.numbere.property❗ Answer: d.
❓ Question: If we call the class with
let myCar = new Car('Tesla', 'Model S', 2019), what is output byconsole.log(myCar)?a.
Car {make: 'Tesla', model: 'Model S', year: 2019, color: undefined, mpg: undefined }b.Car {make: 'Tesla', model: 'Model S', year: 2019, color: '', mpg: '' }c.Car {make: 'Tesla', model: 'Model S', year: 2019 }❗ Answer: a.
Just as with objects, we may want to add methods to our classes in addition to properties. So far, we have learned how to set values of the class's properties inside the constructor.
When assigning methods in classes, we can either create them outside or inside the constructor.
When assigning methods outside of the constructor, we simply declare our methods the same way we would normally do for objects.
class ClassName {
constructor(parameters){
// assign properties with this.key = value;
}
methodName(parameters){
// function code
}
}Example:
class Astronaut { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; } reportStats() { let stats = `${this.name} is ${this.age} years old and has a mass of ${this.mass} kg.`; return stats; } } let fox = new Astronaut('Fox', 7, 12); console.log(fox.reportStats());Fox is 7 years old and has a mass of 12 kg.We declared or method,
reportStats()outside of the constructor. When we delare a new instance of theAstronautclass, we can use thereportStats()method to return a concise string containing all of the info we would need about an astronaut.
When declaring methods inside the constructor, we need to make use of the this keyword, just as we would with or properties.
class ClassName {
constructor(parameters){
this.methodName = function(parameters){
// function code
}
}
}Example: Let's consider the
Astronautclass that we have been working with. When assigning the methodreportStats(), inside theconstructor, we would do so like this:class Astronaut { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; this.reportStats = function() { let stats = `${this.name} is ${this.age} years old and has a mass of ${this.mass} kg.`; return stats; } } } let fox = new Astronaut('Fox', 7, 12); console.log(fox.reportStats());Fox is 7 years old and has a mass of 12 kg.Initially, this may see to produce teh same result as assigning
reportStats()outside the constructor. We will weigh the prose and cons of both methods below.
Try It!: Try comparing the outputs of
foxandhippoto see the effect of assigning a method inside the constructor versus outside the constructor. Link// Here we assign the method inside the constructor class AstronautI { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; this.reportStats = function() { let stats = `${this.name} is ${this.age} years old and has a mass of ${this.mass} kg.`; return stats; } } } // Here we assign the method outside of the constructor class AstronautO { constructor(name, age, mass){ this.name = name; this.age = age; this.mass = mass; } reportStats() { let stats = `${this.name} is ${this.age} years old and has a mass of ${this.mass} kg.`; return stats; } } let fox = new AstronautI('Fox', 7, 12); let hippo = new AstronautO('Hippo', 25, 1000); console.log(fox); console.log(hippo);
IN the case of assigning the method inside the constructor, each Astronaut object carries around the code for reportStatus(). With today's computers, this is a relatively minor concern. However, each Astronaut has extra code that may not be needed. This consumes memory, which you need to consider since today's businesses want efficient code that doesn't tax their systems.
Plus there could be the possibly you could be working at some company that is still using Windows XP (and STILL won't let it die!), still stores all their data in COBOL, and is run by some guy who has been there so long he remembers when IBM used to be called CTR.
Because of this (and that other stuff), if a method is the same for ALL objects of a class, define that method outside of the constructor. Each object does not need a copy of identical code. Therefore, the declaration of a method outside of the constructor will not consume as much memory.
NERD NOTE: I'm not sure why, but the prhase "late binding" popped into my head here. It's probaby not relevant here, but it is something to research later.
Elsa: "I knew it was you. You have your father's eyes."
Indiana: "And my mother's ears, but the rest belongs to you."
--Indiana Jones and the Last Crusade (1989)
Object-oriented programming (OOP), is a software paradigm (type of software design, or more appropriately a software design method) where the codebase is organized around objects and classes. Objects contain the functions and the central logic of a program.
OOP stands on top of four principals: abstraction, polymorphism, encapsulation, and inheritance.
We will dive into inheritance now and work with the other three principles in Unit TWo of this class.
Inheritance refers to the ability of one class to acquire properties and methods from another.
Think of it this way, in the animal kingdom, a species is a unique entity that inherits traits from its genus. The genus also has unique properties, but inherits traits from its family. For example, a tiger and a house cat are two different species, however they share similar traits such as retractable claws. The two cats inherted their similar traits from their shared family, felidae.
🧪 SCIENCE NOTE: Taxonomic rank is way more complicated than this example.
Using inheritance in programming, we can create a structure of classes that inherit properties and methods from other classes.
If we wanted to program classes for our tiger and housecat, we could create a felidae class for the family. We would then create two classes for the panthera genus and the felis genius. We would create classes for the tiger and housecat species as well. The species classes would inherit properties and methods from the genus classes and the genus classes would inherit properties and methods from the family class.
classDiagram
class Felidae {
this.claws = "retractable";
}
class Panthera {
this.roar = "loud";
}
class Felis {
this.pupils = "vertical";
}
class Tiger {
this.hasStripes = true;
}
class Housecat {
this.personality = "jugemental";
}
Felidae <|-- Panthera
Felidae <|-- Felis
Panthera <|-- Tiger
Felis <|-- Housecat
🧜♀️ MERMAID NOTE: Normally we would use
hasStripes = trueinstead ofthis.hasStripes = true;in UML Class diagrams, but in the above class diagram, I am making an exception.
The classes inheriting properties and methods are child classes, and the classes passing down properties and methods are parent classes.
UML NOTE: As strange as it looks in a UML class diagram, the arrows indicating inheritance ALWAYS point from the child class to the parent class, and parents should generally be above their children whenever possible.
When designating a class as the child class of another class in JavaScript, we use the extends keyword. We must also use the super() constructor to get the properties and methods from the parent class.
class ChildClass extends ParentClass {
constructor(){
super();
// properties
}
}In the case of a tiger, tigers have stripes, but they also have a loud roar! Their ability to roar loudly is a trait shared with other members of the panthera genus. Tigers also get their retractable claws from the felidae family.
Example (more like Try It!): Link
class Felidae { constructor() { this.claws = "retractable"; } } class Panthera extends Felidae { constructor() { super(); this.roar = "loud"; } } class Tiger extends Panthera { constructor() { super(); this.hasStripes = "true"; } } let tigger = new Tiger(); console.log(tigger);When creating the classes for our tiger, we can use the
extendskeyword to set upTigeras the child class ofPanthera. TheTigerclass inherits the propertyroarfrom thePantheraclass and has an additional propertyhasStripes.
NOTE: The
extendskeyword is not supported in Internet Explorer. On the other hand, why would anyone use Internet Explorer?
#LaunchCode
