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:

  1. class PersonClass {
  2. // equivalent of the PersonType constructor
  3. constructor(name) {
  4. this.name = name;
  5. }
  6. // equivalent of PersonType.prototype.sayName
  7. sayName() {
  8. console.log(this.name);
  9. }
  10. }
  11. let person = new PersonClass("Nicholas");
  12. person.sayName(); // outputs "Nicholas"
  13. console.log(person instanceof PersonClass); // true
  14. console.log(person instanceof Object); // true
  15. console.log(typeof PersonClass); // "function"
  16. 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:

  1. 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.
  2. All code inside of class declarations runs in strict mode automatically. There’s no way to opt-out of strict mode inside of classes.
  3. 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.
  4. All methods lack an internal [[Construct]] method and will throw an error if you try to call them with new.
  5. Calling the class constructor without new throws an error.
  6. 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:

  1. // direct equivalent of PersonClass
  2. let PersonType2 = (function() {
  3. "use strict";
  4. const PersonType2 = function(name) {
  5. // make sure the function was called with new
  6. if (typeof new.target === "undefined") {
  7. throw new Error("Constructor must be called with new.");
  8. }
  9. this.name = name;
  10. }
  11. Object.defineProperty(PersonType2.prototype, "sayName", {
  12. value: function() {
  13. // make sure the method wasn't called with new
  14. if (typeof new.target !== "undefined") {
  15. throw new Error("Method cannot be called with new.");
  16. }
  17. console.log(this.name);
  18. },
  19. enumerable: false,
  20. writable: true,
  21. configurable: true
  22. });
  23. return PersonType2;
  24. }());

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.