Work with Classes
Learning Objectives
After completing this unit, you'll be able to:
- Explain what's different about how you create and invoke classes in ES6+.
- List the commonly used members of a class.
- Distinguish between a base and derived class.
If It Looks Like a Class…
Prior to ES6, if you wanted to create a class in JavaScript, you used prototypes to do something similar to this:
function Animal(name) { this.name = name; } Animal.prototype.printName = function() { console.log(this.name); }
In that example, a prototype-based constructor function named Animal
was created. An uppercase “A” was used in the function name and arguments were assigned to the this
keyword. This is the only clue that lets other developers know that Animal
is not just another function. If you wanted to instantiate the class and call the printName
function, you would do it like this:
let duck = new Animal('duck'); duck.printName(); // Displays "duck"
If you are coming from another language, then you are probably used to class-based inheritance. In a nutshell, JavaScript does not work like that. In an attempt to make JavaScript at least appear to work like a class-based language, ES6 introduced the class
keyword. Using this new syntax means that the Animal
class can now be defined like this:
class Animal { constructor(name) { this.name = name; } printName() { console.log(this.name); } }
What is important to know is that even though the class
keyword is used, the underlying object created is still a function. Executing the following code would show “function” and not “class” in the console as the type.
console.log(typeof Animal); // Display "function"
Instantiating and using methods from the class works exactly the same as it did for constructor functions. Well, kind of. Using the new keyword was optional for ES5 constructor functions, but now is required. If you try to leave off the new
keyword when working with classes, a TypeError will be thrown.
Another difference is that function declarations can be hoisted. In other words, you can call a function that has yet to be declared. Classes do not allow this sort of thing. A class can only be accessed after its definition is evaluated.
Classes can contain the following kinds of members.
Member |
Description |
Example |
---|---|---|
Constructor |
The constructor is executed automatically when creating a new instance of the class. It guarantees that an initialization function is always called. This helps maintain a valid state for the class. But you don't have to create a constructor. If one is not included, then the JavaScript engine creates an empty one for you. |
constructor(name) { this.name = name; } |
Static Methods |
Static methods are not part of any instance of the class, meaning that you can refer to these methods without referring to an instance. The concept of static class members is not new to ES6, but the |
static methodName() { return 'something'; } |
Prototype Methods |
These methods do not include the |
printName() { console.log(this.name); } |
Getters and Setters |
These accessor functions work just like object literals and work the same as they did in ES5. Essentially you just put the |
get area() { return this.height * this.width; } set area(value) { this.area = value; } |
The most important thing to remember is that even though ES6 classes look like traditional object-oriented classes, they are not. Even with the new class
keyword, they are still based on JavaScript's prototypal inheritance.
Speaking of Inheritance
Since we mentioned inheritance, you may be wondering how that works with the new syntax. You will be happy to know that ES6 classes make inheritance much easier and more intuitive.
Classes come in two flavors: base classes and derived classes. The difference is the extends
keyword. Derived classes (also known as subclasses) have them, and base classes don't. Take, for example, the following base class named Parent
.
class Parent { constructor(name) { this.name = name; } getName() { return this.name; } }
Assume you needed to create a subclass named Child
that extends the functionality available in the Parent
class. It might look something like this:
class Child extends Parent { constructor(name) { super(name); } getMessage() { return 'Hello ' + super.getName(); } }
let someone = new Child('person'); console.log(someone.getMessage()); // Displays "Hello person"
Copying all that code into PlayCode results in the phrase “Hello person” printed to the console. The extends
keyword in the Child
class definition tells you it's a derived class.
Also notice the use of the super
keyword, which allows you to reference the parent constructor and the method definitions from the base class. Whenever you see the super
keyword, you know you are in a derived class and referring to the base class.
Tell Me More
- Although commas are used to separate method definitions in objects, they are not allowed in classes.
- Classes can also be defined using expressions:And the printName method can be accessed like this:
const myAnimal = class Animal { constructor(name) { this.name = name; } printName() { console.log(this.name); } }
let duck = new myAnimal('duck'); duck.printName(); // Displays "duck"
Resources
- MDN Web Docs: Object
- MDN Web Docs: Classes
- MDN Web Docs: Class expression
- MDN Web Docs: Static keyword
- sitepoint: Object-oriented JavaScript: A Deep Dive into ES6 Classes
- 2ality: Classes in ECMAScript6 (final semantics)