Skip to content

Instantly share code, notes, and snippets.

@colonelrascals
Created May 22, 2017 18:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colonelrascals/9a004335f64d0199f11c68eb94309585 to your computer and use it in GitHub Desktop.
Save colonelrascals/9a004335f64d0199f11c68eb94309585 to your computer and use it in GitHub Desktop.
## Classes
Classes are like blueprints for creating Objects in a language. The object created using a class is called an instance. That said, the `class` keyword in JavaScript is new to ES6. This new keyword (`class`) makes using what we call "prototypes" easier than in the past, while glossing over some of the intricacies. A "prototype" in JavaScript is actually just a "fallback" mechanism. If the instance does not have a particular property or method, then JavaScript will "fall back" to the prototype and look there. Don't get too caught up in this "prototype chain" now, you'll read more about this later.
> Note that "classes" in JavaScript are very different from other languages. The underlying differences are a bit complex, and for now you can think of the two paradigms as the same, but know that in the future you might need to explore those differences.
We call JavaScript's object system "prototypical" (or "prototypal") whereas the object system in Java, for example, would be called "class-based". Don't be confused by the nomenclature! The reason they are called "Classes" in JavaScript is simply to make it easier for newcomers to JavaScript (this is called "syntactic sugar"). Under the covers, this is all about what we call the "prototype chain" (you can read more about that at the end).
In this lesson we'll start with the `class` keyword in JavaScript, and later we'll discuss the more complicated "prototype" format.
### A Basic Class
We start off a basic class definition with the `class` keyword, then the name of the class and a code block. The **name of a class should be capitalized**. This is common convention and will signal to other developers that they can create `new` instances of that object. Notice that there is also a special block _inside_ the class block called `constructor`. Don't be fooled by the lack of the `function` keyword, this is creating a new function! However, it is creating that function _on the Employee class_. When we create `new` Employees, this function will be called to set up any initial data.
```js
class Employee {
constructor (name) {
// we put code in here to initially create the object
this.name = name; // `this` inside any method refers to the object being created!
}
}
```
Notice that we used the special variable `this` inside the constructor. That variable will always exist for instance methods (functions on an object) and will be set to equal the newly created instance. In our case, `this` inside the constructor is the newly created `Employee`. Now that we have the class defined, we can create an instance of `Employee` from above like so:
```js
let me = new Employee('Jordan');
console.log(me.name); // will print out: "Jordan"
```
We can request as many arguments as we want in the constructor definition, and what we do with them is our choice! The `class` is new in ES6 and is an easier way to construct objects than in ES5. You can create the same `Employee` in ES5 using a named constructor function like so:
```javascript
function Employee(name) {
this.name = name;
}
let me = new Employee('Jordan');
```
This syntax is exactly equivalent to what we had earlier with the `class` keyword, but we'll soon see how our new syntax can allow for quicker definition of other methods on the object. Because the new `class` keyword and structure is the same under the covers as what we had before in ES5 we call it **"syntactic sugar"**: something that abstracts an existing functionality with code that is easier to use or understand.
### Adding methods
Our objects will get more complex over time, including multiple different properties and methods. A "method" is simply the name for a function that exist _on an object_ versus functions that exist purely on their own. Here is a `class` with a couple of methods:
```js
class Employee {
constructor (name, salary = 0) {
this.name = name;
this.salary = salary;
this.status = 'hired';
}
giveRaise (amount = 0) {
this.salary += amount;
}
}
```
Note how we define a new method on our `Employee` class - we give it a name, but we still do not use the `function` keyword. We still have our argument list (in parenthesis), and in this case our `giveRaise` method accepts one argument: the amount to raise the employee's salary by. Notice that the method uses that special variable called `this` inside to refer to the current instance. In the constructor `this` points to the newly created object (instance), and in the methods it is very similar, it is whatever object we are acting upon. Let's see an example:
```js
let me = new Employee('Jordan', 30000);
me.giveRaise(5000);
console.log( me.salary ); // will print 35000
```
Our use of the method like so: `me.giveRaise(5000)` means that _inside_ the `giveRaise` function code `this` will point to the same object that `me` points to. We call this the **functions "context"**. And it will almost always point to whatever object appears before the dot (".") in the function call.
#### The Old Way
This new `class` syntax makes things easier for us as developers, but it is important to be familiar with how things "really work". Under the covers JavaScript is adding methods to what we call the "prototype" object of the constructor function. If we wanted to add the `giveRaise` method using ES5 syntax we would do so this way:
```js
function Employee(name) {
this.name = name;
}
Employee.prototype.giveRaise = function giveRaise(amount) {
this.salary += amount;
}
```
Notice how we access the `prototype` property of the `Employee` object - yes, functions are also objects! If you are confused about this syntax, you are not alone. That's one of the reasons that the language has adopted the `class` syntax, but remember: **it is just syntactic sugar**. If we ever need to debug an issue with our `class` or its instances we're probably going to need to know about the `prototype` object. **You can read more about prototypes further down.**
#### Static Methods
We can also create "static" methods on a class. These methods _do not_ apply to a particular instance, but rather to the type of thing it is. For example, all of my employees work at the same company. I could create a property on those objects to track which company, but if they're all the same there is no need! Instead, I can create a `static` method to return this information:
```js
class Employee {
constructor () { /* ... */ }
static getCompany () {
return 'The Iron Yard';
}
}
let me = new Employee();
Employee.getCompany(); // this will return "The Iron Yard"
me.getCompany(); // this will produce a ReferenceError!
```
Notice how an instance of the `Employee` class _does not_ have the `getCompany()`, only the class itself can use that method.
> In ES5 we define static methods on the constructor function object very similar to how we did above. For example, something like: `Employee.getCompany = function getCompany() { return 'The Iron Yard'; }`
### Inheritance and Extending
There are many times where we will want to share functionality between one class and many others. For example, we have an "Employee" above, but we might have different kinds of employees that can do different things. For example, a "manager" should be able to hire new employees and might have access to other information. An "executive" might be able to do other things. However, all of these people share the fact that they have a "name" and a "salary". To accomplish this we use a concept call "inheritance". That basically just means that one thing **is a type of** another thing. For example, a dog is a type of animal... but so are humans! We call the "dog" in this case the **"child" class** and the "animal" the **"parent" class**.
Here's how we implement inheritance using the ES6 `class` keyword:
```js
class Employee {
constructor (name) {
this.name = name;
this.status = 'hired';
}
terminate () {
this.status = 'fired';
}
}
class Manager extends Employee {
constructor (name) {
super(name); // call the PARENT's constructor first
this.team = []; // maybe we need to track a managers team!
}
hireTeamMember (name) {
this.team.push( new Employee(name) );
}
}
```
In the example above we can see that the `Manager` class **`extends`** the `Employee` class. That means it shares all the same stuff as Employee, but add more of its own! In the code below we can see how a `Manager` can hire a team member, but a regular Employee cannot. Yet they can _both_ be terminated.
```js
let me = new Manager('Jordan');
let jane = new Employee('Jane Doe');
me.hireTeamMember('Rachel');
console.log( me.team ); // [ "Rachel" ]
jane.hireTeamMember('Billy'); // ReferenceError
me.terminate();
console.log( me.status ); // "fired"
jane.terminate();
console.log( jane.status ); // "fired"
```
#### In Ye Olden Times
Inheritance with ES5 gets pretty verbose and complicated - another reason for the new `class` and `extends` syntactic sugar. Below is a brief example of how we would do the `Employee` and `Manager` code we see above.
```js
function Employee(name) { /* define all the things about an "Employee" instance */ }
Employee.prototype.giveRaise = function giveRaise() { /* some code here... */ }
function Manager(name) {
// Call the parent constructor first...
Employee.apply(this, [name]);
// now define all the things specific to a "Manager" instance
}
// Now we connect the Manager constructor to the Employee constructor through their prototypes...
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
// Now we can add new things to the Manager prototype...
Manager.prototype.terminate = function terminate() { /* some code here... */ }
```
We can see that this is much more complicated than our ES6 version, but again, we need to at least be familiar with the role that `prototype` plays in this discussion. You can read more about the `prototype` as it applies to inheritance further below.
#### Calling Parent Methods
You might have noticed that we called the special function `super()` inside our constructor for the `Manager` (in the ES6 version). This special function will execute the constructor method on the **parent class**. However, we can also use it in other methods to refer to the method _of the same name_ on the parent class. Here is an example of that:
```js
class Employee {
constructor (name) { /* ... */ }
terminate () {
this.status = 'fired';
}
}
class Manager extends Employee {
constructor (name) { /* ... */ }
terminate (severance) {
super.terminate(); // this will call the `terminate()` method on Employee!
this.bonus = severance;
}
}
```
#### Determining an Object's Class
There are many times when we will want to know what `class` an object was created from. For example, if we expect to receive a `Date` as input, then we should confirm that the argument was, in fact, a `Date` instance! We can't use `typeof()` for this because `typeof()` only provides the raw data type (like "number", "string", or "object") and not the specific "class" of object. For that we use `instanceof` - but it's not a function, it's a logical operator.
```js
/**
* Gets the time difference in milliseconds from now
* @param {Date} start The Date to tell the difference from now
* @return {number} The number of milliseconds different between then and now
*/
function getTimeDifference(start) {
if (!(start instanceof Date)) {
return null;
}
return Date.now() - start.getTime();
}
```
We can check any object to see what created it, and the `instanceof` operator is also aware of the inheritance chain (what we would typically call the "prototype chain"):
```js
let me = new Manager('Jordan');
console.log( me instanceof Manager ); // true
console.log( me instanceof Employee ); // true
console.log( me instanceof Object ); // true
console.log( me instanceof Date ); /...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment