Chapter 9: Classes

9.1 Class basic syntax

The “class” syntax

class MyClass {
  prop = value; // property

  constructor(...) { // constructor
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter method
  set something(...) {} // setter method

  [Symbol.iterator]() {} // method with computed name (symbol here)
  // ...
}

The constructor() method is called automatically by new, so we can initialize the object there. There's no comma between class methods.

What is a class?

In JavaScript, a class is a kind of function. What class User {...} construct really does is:

  1. Creates a function named User, that becomes the result of the class declaration. The function code is taken from the constructor method (assumed empty if we don’t write such method).

  2. Stores class methods, such as sayHi, in User.prototype.

Not just a syntactic sugar

  1. A function created by class is labelled by a special internal property [[FunctionKind]]:"classConstructor".

  2. Unlike a regular function, a class must be called with new.

  3. Class methods are non-enumerable. A class definition sets enumerable flag to false for all methods in the prototype.

  4. All code inside the class construct is automatically in strict mode.

Class Expression

Dynamically make a class on-demand:

Getters/setters

classes may include getters/setters, computed properties etc.

Such class declaration works by creating getters and setters in the prototype.

Computed names […]

Class fields

"Class fields" is a syntax that allows to add any properties. They are set on individual objects, not the prototype.

Making bound methods with class fields

Instead of using a wrapper-function or bind the method to object, we can use arrow functions to solve the "losing this" problem. The function is created on a per-object basis.

9.2 Class inheritance

The “extends” keyword

The syntax to extend another class is: class Child extends Parent. Any expression is allowed after extends.

Internally, extends keyword works using the good old prototype mechanics. It sets Rabbit.prototype.[[Prototype]] to Animal.prototype.

Overriding a method

Usually we don’t want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality.

  • super.method(...) to call a parent method.

  • super(...) to call a parent constructor (inside our constructor only).

Arrow functions have no super. It’s taken from the outer function.

Overriding constructor

If a class extends another class and has no constructor, then the following “empty” constructor is generated:

Constructors in inheriting classes must call super(...), and do it before using this.

Explain: A derived constructor has a special internal property [[ConstructorKind]]:"derived".

  • When a regular function is executed with new, it creates an empty object and assigns it to this.

  • When a derived constructor runs, it expects the parent constructor to do the job.

Overriding class fields

Here's the tricky part:

There’s no own constructor in Rabbit, so Animal constructor is called. Parent constructor always uses its own field value, not the overridden one.

Explain: The class field is initialized:

  • Before constructor for the base class (that doesn’t extend anything).

  • Imediately after super() for the derived class.

So that when the Rabbit constructor implicity calls super(), its class field has not been initialized yet, and that’s why the parent fields are used.

Super: internals, [[HomeObject]]

[[HomeObject]]

When a function is specified as a class or object method, its [[HomeObject]] property becomes that object. When super() is called, it takes the parent method from the prototype of its [[HomeObject]], without using this.

Methods are not “free”

[[HomeObject]] can’t be changed, so this bond is forever. If a method does not use super, then we can still consider it free and copy between objects. But with super things may go wrong.

Methods, not function properties

[[HomeObject]] is defined for methods both in classes and in plain objects. For objects, methods must be specified exactly as method(), not as "method: function()".

9.3 Static properties and methods

We can also assign a method to the class function itself, not to its prototype.

9.4 Private and protected properties and methods

It’s always convenient when implementation details are hidden, and a simple, well-documented external interface is available.

Internal and external interface

  • Public: accessible from anywhere. They comprise the external interface.

  • Private: accessible only from inside the class. These are for the internal interface.

Protected properties

Protected properties are usually prefixed with an underscore _. That is not enforced on the language level, but there’s a well-known convention.

Protected fields are inherited.

Private properties

The recent JavaScript proposal provides language-level support for private properties and methods.

  • Privates should start with #.

  • They are only accessible from inside the class.

  • We can’t access them from outside or from inheriting classes.

  • Private fields do not conflict with public ones.

  • Private fields are not available as this[name].

9.5 Extending built-in classes

Built-in classes like Array, Map and others are extendable.

Built-in methods like filter, map and others return new objects of exactly the inherited type PowerArray. Their internal implementation uses the object’s constructor property for that.

We can add a special static getter Symbol.species to the class. If it exists, it should return the constructor that JavaScript will use internally to create new entities in map, filter and so on.

There's no static inheritance in built-ins.

9.6 Class checking: "instanceof"

The instanceof operator

It returns true if obj belongs to the Class or a class inheriting from it.

Normally, instanceof examines the prototype chain for the check. We can also set a custom logic in the static method Symbol.hasInstance.

Workflow of "instanceof"

  1. If there’s a static method Symbol.hasInstance, then just call it: Class[Symbol.hasInstance](obj).

  2. Checks whether Class.prototype is equal to one of the prototypes in the obj prototype chain.

Symbol.toStringTag

The behavior of Object toString can be customized using a special object property Symbol.toStringTag.

9.7 Mixins

Mixin: a class that contains methods for other classes. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.

A mixin example

Mixins can also make use of inheritance inside themselves. super looks for parent methods in [[HomeObject]].[[Prototype]].

Last updated