Class Declarations
The simplest class form in ECMAScript 6 is the class declaration, which looks similar to classes in other languages.
A Basic Class Declaration
Class declarations begin with the class
keyword followed by the name of the class. The rest of the syntax looks similar to concise methods in object literals, without requiring commas between them. For example, here’s a simple class declaration:
class PersonClass {
// equivalent of the PersonType constructor
constructor(name) {
this.name = name;
}
// equivalent of PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // outputs "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"
The class declaration PersonClass
behaves quite similarly to PersonType
from the previous example. But instead of defining a function as the constructor, class declarations allow you to define the constructor directly inside the class with the special constructor
method name. Since class methods use the concise syntax, there’s no need to use the function
keyword. All other method names have no special meaning, so you can add as many methods as you want.
I> Own properties, properties that occur on the instance rather than the prototype, can only be created inside a class constructor or method. In this example, name
is an own property. I recommend creating all possible own properties inside the constructor function so a single place in the class is responsible for all of them.
Interestingly, class declarations are just syntactic sugar on top of the existing custom type declarations. The PersonClass
declaration actually creates a function that has the behavior of the constructor
method, which is why typeof PersonClass
gives "function"
as the result. The sayName()
method also ends up as a method on PersonClass.prototype
in this example, similar to the relationship between sayName()
and PersonType.prototype
in the previous example. These similarities allow you to mix custom types and classes without worrying too much about which you’re using.
Why to Use the Class Syntax
Despite the similarities between classes and custom types, there are some important differences to keep in mind:
- Class declarations, unlike function declarations, are not hoisted. Class declarations act like
let
declarations and so exist in the temporal dead zone until execution reaches the declaration. - All code inside of class declarations runs in strict mode automatically. There’s no way to opt-out of strict mode inside of classes.
- All methods are non-enumerable. This is a significant change from custom types, where you need to use
Object.defineProperty()
to make a method non-enumerable. - All methods lack an internal
[[Construct]]
method and will throw an error if you try to call them withnew
. - Calling the class constructor without
new
throws an error. - Attempting to overwrite the class name within a class method throws an error.
With all of this in mind, the PersonClass
declaration from the previous example is directly equivalent to the following code, which doesn’t use the class syntax:
// direct equivalent of PersonClass
let PersonType2 = (function() {
"use strict";
const PersonType2 = function(name) {
// make sure the function was called with new
if (typeof new.target === "undefined") {
throw new Error("Constructor must be called with new.");
}
this.name = name;
}
Object.defineProperty(PersonType2.prototype, "sayName", {
value: function() {
// make sure the method wasn't called with new
if (typeof new.target !== "undefined") {
throw new Error("Method cannot be called with new.");
}
console.log(this.name);
},
enumerable: false,
writable: true,
configurable: true
});
return PersonType2;
}());
First, notice that there are two PersonType2
declarations: a let
declaration in the outer scope and a const
declaration inside the IIFE. This is how class methods are forbidden from overwriting the class name while code outside the class is allowed to do so. The constructor function checks new.target
to ensure that it’s being called with new
; if not, an error is thrown. Next, the sayName()
method is defined as nonenumerable, and the method checks new.target
to ensure that it wasn’t called with new
. The final step returns the constructor function.
This example shows that while it’s possible to do everything classes do without using the new syntax, the class syntax simplifies all of the functionality significantly.
A> ### Constant Class Names A> A> The name of a class is only specified as if using const
inside of the class itself. That means you can overwrite the class name outside of the class but not inside a class method. For example: A> A> js A> class Foo { A> constructor() { A> Foo = "bar"; // throws an error when executed A> } A> } A> A>// but this is okay after the class declaration A> Foo = "baz"; A>
A> A> In this code, the Foo
inside the class constructor is a separate binding from the Foo
outside the class. The internal Foo
is defined as if it’s a const
and cannot be overwritten. An error is thrown when the constructor attempts to overwrite Foo
with any value. But since the external Foo
is defined as if it’s a let
declaration, you can overwrite its value at any time.