6 KiB
Classes
The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax. It also introduces new great features, useful for object-oriented programming.
The "class" syntax
The class
syntax is versatile, we'll start with 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. Just please note that methods in a class do not have a comma between them. Novice developers sometimes forget it and put a comma between class methods, and things don't work. That's not a literal object, but a class syntax.
What is a class?
So, what exactly is a class
? We may think that it defines a new language-level entity, but that would be wrong.
In Javascript, a class is a kind of a function.
The definition class User {...}
create such function and puts the methods into User.prototype
. So the structure is similar.
Here's the code to dig into the class and see that:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
*!*
// proof: User is a function
alert(typeof User); // function
*/!*
*!*
// 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 what class User
creates:
So class
is a special syntax to define a constructor together with its prototype methods. There are also quite a few additional features here and there, we'll study them later on.
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 makeClass(phrase) {
*!*
// declare a class and return it
return class {
sayHi() {
alert(phrase);
};
};
*/!*
}
let User = makeClass("Hello");
new User().sayHi(); // Hello
That's quite normal if we recall that class
is just a special form of a function-with-prototype definition.
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
// "Named Class Expression" (alas, no such term, but that's what's going on)
let User = class *!*MyClass*/!* {
sayHi() {
alert(MyClass); // MyClass is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass not visible outside of the class
Differences of classes vs functions
Classes have some differences compared to regular functions:
- Constructors require
new
- Unlike a regular function, a class
constructor
can't be called withoutnew
:
class User {
constructor() {}
}
alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
- Different string output
- 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 methods are non-enumerable
- A class definition sets
enumerable
flag tofalse
for all methods in the"prototype"
. That's good, because if wefor..in
over an object, we usually don't want its class methods. - Classes have a default
constructor() {}
- If there's no
constructor
in theclass
construct, then an empty function is generated, same as if we had writtenconstructor() {}
. - Classes always
use strict
- All code inside the class construct is automatically in strict mode.
Getters/setters, other shorthands
Classes also include getters/setters, generators, computed properties etc.
Here's an example for user.name
implemented using get/set
:
class User {
constructor(name) {
// invokes the setter
this.name = name;
}
*!*
get name() {
*/!*
return this._name;
}
*!*
set name(value) {
*/!*
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name too short.
Internally, getters and setters are created on User.prototype
, like this:
Object.defineProperties(User.prototype, {
name: {
get() {
return this._name
},
set(name) {
// ...
}
}
});
Here's an example with computed properties:
function f() { return "sayHi"; }
class User {
[f()]() {
alert("Hello");
}
}
new User().sayHi();
For a generator method, similarly, prepend it with *
.
Class properties
Class-level properties is a recent addition to the language.
In the example before, User
only had methods. Let's add a property:
class User {
name = "Anonymous";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi();
The property is not placed into User.prototype
. Instead, it is created by new
, separately for every object. So, the property will never be shared between different objects of the same class.
Summary
The basic class syntax looks like this:
class MyClass {
prop = value;
constructor(...) {
// ...
}
method(...) {}
get something(...) {}
set something(...) {}
[Symbol.iterator]() {}
// ...
}
MyClass
is technically a function, while methods are written to MyClass.prototype
.
In the next chapters we'll learn more about classes, including inheritance and other features.