By Dmitri Pavlutin
Translation: Crazy geek
Original text: dmitripavlutin.com/javascript-…
Reproduced without permission
JavaScript uses prototype inheritance: each object inherits properties and methods from its prototype object.
There are no traditional classes in JavaScript that serve as blueprints for creating objects as used in languages such as Java or Swift, and prototype inheritance deals only with objects.
Archetypal inheritance can mimic the inheritance of classical classes. To bring traditional classes to JavaScript, the ES2015 standard introduces class syntax: syntactic sugar based on archetypal inheritance.
This article familiarizes you with JavaScript classes: how to define classes, initialize instances, define fields and methods, understand private and public fields, and master static fields and methods.
directory
[toc]
1. Definition:classThe keyword
Define a class in JavaScript with the special class keyword:
class User {
// The body of class
}
Copy the code
The above code defines a class User. Curly braces {} define the body of the class. Note that this syntax is called a class declaration.
You are not obligated to specify the name of the class. By using class expressions, you can assign classes to variables:
const UserClass = class {
// The body of class
};
Copy the code
You can easily export classes as part of an ES2015 module. This is the syntax for the default export:
export default class User {
// The body of class
}
Copy the code
There is also a named export:
export class User {
// The body of class
}
Copy the code
This class becomes useful when you create instances of it. An instance is an object that contains the data and behavior described by the class.
The new operator instantiates the Class in JavaScript: instance = new Class().
For example, you can instantiate the User class with the new operator:
const myUser = new User();
Copy the code
New User() creates an instance of the User class.
2. Initialization:constructor()
Constructor (param1, param2…) Is a special method for initializing instances of a class. Here you can set the initial value of the field or do any type of setting for the object.
In the following example, the constructor sets the initial value of field name:
class User {
constructor(name) { this.name = name; }}
Copy the code
The constructor of User takes only one parameter, name, to set the initial value of the this.name field.
In the constructor, the this value is equal to the newly created instance.
The arguments used to instantiate the class become constructor arguments:
class User {
constructor(name) {
name; // => 'Jon Snow' this.name = name;}}const user = new User('Jon Snow');
Copy the code
The name parameter in the constructor has a value of ‘Jon Snow’.
If you do not define a constructor for the class, a default constructor is created. The default constructor is an empty function that does not modify the instance.
Also, a JavaScript class can have at most one constructor.
3. The field
Class fields are variables used to hold information. Fields can be attached to 2 entities:
- Field on the class instance
- Fields of the class itself (also known as static)
These fields also have level 2 accessibility:
- Public: This field can be accessed anywhere
- Private: This field can only be accessed in the course body
3.1 Public instance Fields
Let’s look at the previous code snippet again:
class User {
constructor(name) {
this.name = name; }}Copy the code
The expression this.name = name creates an instance field name and assigns an initial value to it.
Later, you can use the property accessor to access the name field:
const user = new User('Jon Snow');
user.name; // => 'Jon Snow'
Copy the code
Name is a public field that you can access outside of the User class body.
When fields are created implicitly inside the constructor, as in the previous scenario, it can be difficult to master the list of fields. You must decrypt them from the constructor code.
A better approach is to explicitly declare class fields. No matter what the constructor does, the instance always has the same set of fields.
The class field proposal allows you to define fields within the class body. Alternatively, you can immediately indicate the initial value:
class SomeClass {
field1; field2 = 'Initial value';
// ...
}
Copy the code
Let’s modify the User class and declare a public field name:
class User {
name;
constructor(name) {
this.name = name; }}const user = new User('Jon Snow');
user.name; // => 'Jon Snow'
Copy the code
In the class name; A public field name is declared.
Public fields declared this way are very expressive: you can understand the data structure of the class by looking at the field declaration.
Furthermore, class fields can be initialized immediately upon declaration.
class User {
name = 'Unknown';
constructor() {
// No initialization}}const user = new User();
user.name; // => 'Unknown'
Copy the code
Class name =’Unknown’ declares a field name and initializes it with the value ‘Unknown’.
There are no restrictions on access or updates to public fields. You can read their values and assign them to public fields inside the constructor, inside the method, and outside the class.
3.2 Private instance fields
Encapsulation is an important concept that lets you hide the inner details of a class. People who use encapsulated classes are only concerned with the public interface that the class provides and are not coupled to the implementation details of the class.
Classes that consider encapsulation are easier to update when implementation details are changed.
Using private fields is a good way to hide data inside an object. These are fields that can only be read and modified in the class to which they belong. Private fields cannot be changed directly outside the class.
Private fields are accessible only in the body of the class.
Prefix the field name with the special symbol # to make it private, such as #myField. Every time this field is used, the prefix # : must be preserved, whether declared, read, or modified.
Ensure that the field #name can be set once on instance initialization:
class User {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
const user = new User('Jon Snow');
user.getName(); // => 'Jon Snow'
user.#name; // SyntaxError is thrown
Copy the code
#name is a private field. You can access and modify #name within the User body. The method getName() can access the private field #name.
If you try to access the Private field #name outside of the user class body, you will raise a SyntaxError: SyntaxError: Private field ‘#name’ must be declared in an enclosing class.
3.3 Public Static Fields
You can also define fields on the class itself: static fields. It helps define class constants or store class-specific information.
To create a static field in a JavaScript class, use the special keyword static followed by the field name: static myStaticField.
Let’s add a new field type to indicate the user type: admin or Regular. Static fields TYPE_ADMIN and TYPE_REGULAR are constants and can easily distinguish user types:
class User {
static TYPE_ADMIN = 'admin'; static TYPE_REGULAR = 'regular';
name;
type;
constructor(name, type) {
this.name = name;
this.type = type; }}const admin = new User('Site Admin', User.TYPE_ADMIN);
admin.type === User.TYPE_ADMIN; // => true
Copy the code
Static TYPE_ADMIN and static TYPE_REGULAR define static variables in the User class. To access static fields, you must use the class, followed by the field names: user.type_admin and user.type_regular.
3.4 Private Static Fields
Sometimes even static fields are implementation details you want to hide. In this regard, you can make static fields private.
To make a static field private, prefix the field name with the special symbol # : static #myPrivateStaticField.
Suppose you want to limit the number of instances of the User class. To hide details about instance restrictions, create private static fields:
class User {
static #MAX_INSTANCES = 2; static #instances = 0;
name;
constructor(name) {
User.#instances++;
if (User.#instances > User.#MAX_INSTANCES) {
throw new Error('Unable to create User instance');
}
this.name = name;
}
}
new User('Jon Snow');
new User('Arya Stark');
new User('Sansa Stark'); // throws Error
Copy the code
The static field User.#MAX_INSTANCES is used to set the maximum number of instances allowed, while the static field User.# Instances counts the actual number of instances.
These private static fields can only be accessed in the User class. The outside world doesn’t bother with limiting mechanisms: that’s the benefit of encapsulation.
Method 4.
These fields are used to save data. But the ability to modify data is performed by special functions belonging to classes: methods.
JavaScript classes support both instance methods and static methods.
4.1 Instance Method
Instance methods can access and modify instance data. Instance methods can call other instance methods as well as any static method.
For example, let’s define a method getName() that returns the name in the User class:
class User {
name = 'Unknown';
constructor(name) {
this.name = name;
}
getName() { return this.name; }}
const user = new User('Jon Snow');
user.getName(); // => 'Jon Snow'
Copy the code
getName() { … } is a method in the User class. User.getname () is a method call: it executes the method and returns the calculated value, if any.
In class methods and constructors, the value of this is equal to the class instance. Use this to access instance data: this.field, or call another method: this.method().
Let’s add a new method name Contains(string) that takes one argument and calls another method:
class User {
name;
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
nameContains(str) { return this.getName().includes(str); }}
const user = new User('Jon Snow');
user.nameContains('Jon'); // => true
user.nameContains('Stark'); // => false
Copy the code
nameContains(str) { … } is a method of the User class that takes a single argument STR. Not only that, it also gets the username by executing another method of instance this.getName().
Methods can also be private. Methods can be made private with a prefix whose name begins with #.
Let’s make the getName() method private:
class User {
#name;
constructor(name) {
this.#name = name;
}
#getName() { return this.#name; }
nameContains(str) {
return this.#getName().includes(str); }
}
const user = new User('Jon Snow');
user.nameContains('Jon'); // => true
user.nameContains('Stark'); // => false
user.#getName(); // SyntaxError is thrown
Copy the code
#getName() is a private method. In the nameContains(STR) method, you can call a private method like this.#getName().
As a private variable, #getName() cannot be called outside the body of the User class.
4.2 Getter and Setter
Getters and setters mimic regular fields, but have more control over how fields are accessed and modified.
Getters are executed when you try to get the value of a field, and setters are used when you try to set the value.
To ensure that User’s name property cannot be empty, let’s wrap the private field #nameValue in a getter and setter:
class User {
#nameValue;
constructor(name) {
this.name = name;
}
get name() { return this.#nameValue;
}
set name(name) { if (name === '') {
throw new Error(`name field of User cannot be empty`);
}
this.#nameValue = name;
}
}
const user = new User('Jon Snow');
user.name; // The getter is invoked, => 'Jon Snow'
user.name = 'Jon White'; // The setter is invoked
user.name = ''; // The setter throws an Error
Copy the code
When you access the value of field user.name, get name() {… } the getter.
In set name (name) {… } field user.name =’Jon White’ when updated. If the new value is an empty string, the setter throws an error.
4.3 Static Method
Static methods are functions directly attached to a class. They have logic related to classes, not to instances of classes.
To create a static method, use the special keyword static, followed by the general method syntax: static myStaticMethod() {… }.
There are two simple rules to keep in mind when using static methods:
- A static methodYou can visitStatic field
- A static methodDon’t have access toInstance field.
Let’s create a static method to detect whether a User with a specific name has already been used.
class User {
static #takenNames = [];
static isNameTaken(name) { return User.#takenNames.includes(name); }
name = 'Unknown';
constructor(name) {
this.name = name;
User.#takenNames.push(name);
}
}
const user = new User('Jon Snow');
User.isNameTaken('Jon Snow'); // => true
User.isNameTaken('Arya Stark'); // => false
Copy the code
IsNameTaken () is a static method that uses the static private field User.#takenNames to check the adopted name.
Static methods can be private: static #staticFunction () {… }. They also follow the private rule: private static methods can only be called in the body of the class.
5. Inheritance:extends
Classes in JavaScript support single inheritance with the extends keyword.
In the expression Class Child extends Parent {}, subclass Child inherits the constructor \ field and method from its Parent.
For example, let’s create a new subclass, ContentWriter, that extends the parent class User.
class User {
name;
constructor(name) {
this.name = name;
}
getName() {
return this.name; }}class ContentWriter extends User { posts = [];
}
const writer = new ContentWriter('John Smith');
writer.name; // => 'John Smith'
writer.getName(); // => 'John Smith'
writer.posts; / / = > []
Copy the code
ContentWriter inherits the constructor, the getName() method, and the name field from User. Similarly, the ContentWriter class declares a new field, posts.
Note that private members of a parent class are not inherited by subclasses.
5.1 Parent constructor:constructor()In thesuper()
If you want to call a parent constructor in a subclass, you need to use the special function super() provided in the child constructor.
For example, let the ContentWriter constructor call the parent constructor of User and initialize the posts field:
class User {
name;
constructor(name) {
this.name = name;
}
getName() {
return this.name; }}class ContentWriter extends User {
posts = [];
constructor(name, posts) {
super(name); this.posts = posts; }}const writer = new ContentWriter('John Smith'['Why I like JS']);
writer.name; // => 'John Smith'
writer.posts // => ['Why I like JS']
Copy the code
The super(name) in the ContentWriter subclass performs the constructor of the User superclass.
Notice that inside the child constructor, super() must be executed before the this keyword is used. Calling super() ensures that the parent constructor initializes the instance.
class Child extends Parent {
constructor(value1, value2) {
// Does not work!
this.prop2 = value2; super(value1); }}Copy the code
5.2 Parent instance: in the methodsuper
If you want to access the parent method in a child method, you can use the special shortcut super.
class User {
name;
constructor(name) {
this.name = name;
}
getName() {
return this.name; }}class ContentWriter extends User {
posts = [];
constructor(name, posts) {
super(name);
this.posts = posts;
}
getName() {
const name = super.getName(); if (name === ' ') {
return 'Unknwon';
}
returnname; }}const writer = new ContentWriter(' '['Why I like JS']);
writer.getName(); // => 'Unknwon'
Copy the code
The getName() of subclass ContentWriter accesses the method super.getName() directly from the parent User class.
This feature is called method override.
Note that you can also use super with static methods to access parent static methods.
6. Object type check:instanceof
Object instanceof Class is the operator that determines whether an object is an instanceof Class.
Let’s look at the instanceof operator in action:
class User {
name;
constructor(name) {
this.name = name;
}
getName() {
return this.name; }}const user = new User('Jon Snow');
const obj = {};
user instanceof User; // => true
obj instanceof User; // => false
Copy the code
User is an instanceof the user class, and user instanceof user evaluates to true.
The empty object {} is not an instanceof User; the corresponding obj instanceof User is false.
Instanceof is polymorphic: the operator detects a subclass as an instanceof its parent class.
class User {
name;
constructor(name) {
this.name = name;
}
getName() {
return this.name; }}class ContentWriter extends User {
posts = [];
constructor(name, posts) {
super(name);
this.posts = posts; }}const writer = new ContentWriter('John Smith'['Why I like JS']);
writer instanceof ContentWriter; // => true
writer instanceof User; // => true
Copy the code
Writer is an instance of the ContentWriter subclass. The writer instanceof operator is evaluated as true.
ContentWriter is also a subclass of User. Therefore writer instanceof User will also be evaluated to true.
What if you want to determine the exact class of the instance? We can use the constructor attribute and compare it directly to this class:
writer.constructor === ContentWriter; // => true
writer.constructor === User; // => false
Copy the code
7. Classes and prototypes
I must say that the class syntax in JavaScript does a good job of abstracting from stereotype inheritance. To describe class syntax, I don’t even use the term stereotype.
But these classes are built on prototype inheritance. Each class is a function and creates an instance when called as a constructor.
The following two code snippets are equivalent.
Class version:
class User {
constructor(name) {
this.name = name;
}
getName() {
return this.name; }}const user = new User('John');
user.getName(); // => 'John Snow'
user instanceof User; // => true
Copy the code
Version using the prototype:
function User(name) {
this.name = name;
}
User.prototype.getName = function() {
return this.name;
}
const user = new User('John');
user.getName(); // => 'John Snow'
user instanceof User; // => true
Copy the code
If you are familiar with the classic inheritance mechanisms of the Java or Swift languages, you can use the class syntax more easily.
However, even if you use class syntax in JavaScript, I recommend that you know something about prototype inheritance.
8. Availability of class functionality
The course features described in this article relate to ES2015 and phase 3 proposals.
At the end of 2019, the Class functionality is divided into the following two parts:
- Public and private instance fields are part of the class field recommendation
- Private instance methods and accessors are part of the class private method recommendation
- Public and private static fields and private static methods are part of the class static functionality recommendation
- The rest is part of the ES2015 standard.
9. Conclusion
JavaScript classes use constructors to initialize instances and define fields and methods. You can even use the static keyword to attach fields and methods to the class itself.
Inheritance is implemented using the extends keyword: you can easily create children from a parent. The super keyword is used to access the parent class from a subclass.
To use encapsulation, hide the inner details of your class by making fields and methods private. Private field and method names must begin with #.
Classes in JavaScript are becoming easier to use.