en.javascript.info/1-js/07-object-oriented-programming/09-class/article.md
Aman Bangad 40d83c4bc6 bug fix
2017-06-02 11:11:24 -04:00

7 KiB

Classes

The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax.

[cut]

The "class" syntax

The class syntax is versatile, we'll start from a simple example first.

Here's a prototype-based class User:

function User(name) {
  this.name = name;
}

User.prototype.sayHi = function() {
  alert(this.name);
}

let user = new User("John");
user.sayHi();

...And that's the same using class syntax:

class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

let user = new User("John");
user.sayHi();

It's easy to see that the two examples are alike. So, what exactly does class do? We may think that it defines a new language-level entity, but that would be wrong.

The class User {...} here actually does two things:

  1. Declares a variable User that references the function named "constructor".
  2. Puts into User.prototype methods listed in the definition. Here it includes sayHi and the constructor.

Here's some code to demonstrate that:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name);  }
}

*!*
// proof: User is the "constructor" function
*/!*
alert(User == User.prototype.constructor); // true

*!*
// proof: there are two methods in its "prototype"
*/!*
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

Here's the illustration of class User:

So class is a special syntax to define the constructor with prototype methods.

...But not only that. There are minor tweaks here and there to ensure the right usage.

For instance, the constructor function can't be called without new:

class User {
  constructor() {}
}

alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.

Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language.
Class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.

```smart header="Classes always use strict" All code inside the class construct is automatically in strict mode.


### Getters/setters

Classes may also include getters/setters. Here's an example with `user.name` implemented using them:

```js run
class User {

  constructor(name) {
    // invokes the setter
    this.name = name;
  }

*!*
  get name() {
*/!*
    return this._name;
  }

*!*
  set name(value) {
*/!*
    if (value.length < 4) {
      alert("Name too short.");
      return;
    }
    this._name = value;
  }

}

let user = new User("John");
alert(user.name); // John

user = new User(""); // Name too short.

Only methods

Unlike object literals, no property:value assignments are allowed inside class. There may be only methods (without a comma between them) and getters/setters.

The idea is that everything inside class goes to the prototype. And the prototype should store methods only, which are shared between objects. The data describing a concrete object state should reside in individual objects.

If we really insist on putting a non-function value into the prototype, then class can't help here. We can alter prototype manually though, like this:

class User { }

User.prototype.test = 5;

alert( new User().test ); // 5

So, technically that's possible, but we should know why we're doing it.

An alternative here would be to use a getter:

class User {
  get test() {
    return 5;
  }
}

alert( new User().test ); // 5

From the external code, the usage is the same. But the getter variant is probably a bit slower.

Class Expression

Just like functions, classes can be defined inside another expression, passed around, returned etc.

Here's a class-returning function ("class factory"):

function getClass(phrase) {
*!*
  return class {
    sayHi() {
      alert(phrase);
    };
  };
*/!*
}

let User = getClass("Hello");

new User().sayHi(); // Hello

That's quite normal if we recall that class is just a special form of function-with-prototype definition.

And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:

let User = class *!*MyClass*/!* {
  sayHi() {
    alert(MyClass);
  }
};

new User().sayHi(); // works, shows MyClass definition

alert(MyClass); // error, MyClass is only visible in methods of the class

Static methods

Static methods are bound to the class function, not to its "prototype".

An example:

class User {
*!*
  static staticMethod() {
*/!*
    alert(this == User);
  }
}

User.staticMethod(); // true

That actually does the same as assigning it as a function property:

function User() { }

User.staticMethod = function() {
  alert(this == User);
};

The value of this inside User.staticMethod() is the class constructor User itself (the "object before dot" rule).

Usually, static methods are used when the code is related to the class, but not to a particular object of it.

For instance, we have Article objects and need a function to compare them. The natural choice would be Article.compare, like this:

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

*!*
  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
*/!*
}

// usage
let articles = [
  new Article("Mind", new Date(2016, 1, 1)),
  new Article("Body", new Date(2016, 0, 1)),
  new Article("JavaScript", new Date(2016, 11, 1))
];

*!*
articles.sort(Article.compare);
*/!*

alert( articles[0].title ); // Body

Here Article.compare stands "over" the articles, as a means to compare them.

Another example would be a so-called "factory" method, that creates an object with specific parameters.

Like Article.createTodays() here:

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

*!*
  static createTodays() {
    // remember, this = Article
    return new this("Todays digest", new Date());
  }
*/!*
}

let article = Article.createTodays();

alert( articles.title ); // Todays digest

Now every time we need to create a todays digest, we can call Article.createTodays().

Static methods are often used in database-related classes to search/save/remove entries from the database, like this:

// assuming Article is a special class for managing articles
// static method to remove the article:
Article.remove({id: 12345});