“This is the 16th day of my participation in the August More Text Challenge.


Whereas traditional object-oriented languages are class-based, JavaScript is prototype-based. In ES6, the class keyword, while still essentially a constructor, makes it more comfortable for developers to use classes. TypeScript, as a superset of JavaScript, naturally supports all of class’s features, as well as static type detection for class properties, methods, and so on. Let’s take a look at TypeScript class types.

First, the concept of class

1. Use of classes

During development, any entity can be abstracted into an object-like data structure expressed using a class that contains both properties and methods. A coordinate point class can be abstracted in TypeScript like this:

class Point {
  x: number;
  y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
  getPosition() {
    return ` (The ${this.x}.The ${this.y}) `; }}const point = new Point(1.2);
point.getPosition()   / / (1, 2)
Copy the code

Here we define a Point coordinate Point class with two attributes x and y of type number, a constructor function, and a getPosition method. Point is instantiated via new, and the instance is assigned to the variable Point. Finally, the getPosition method defined in the class is called through the instance.

Prior to ES6, we needed to simulate defining classes in the form of a function + prototype chain:

function Point(x, y) {
	this.x = x;
  this.y = y;
}

Point.prototype.getPosition = function() {
  return ` (The ${this.x}.The ${this.y}) `;
}

const point = new Point(1.2);
point.getPosition()   / / (1, 2)
Copy the code

We started by defining the constructor for the Point class, and inside the constructor we defined the x and y properties, followed by adding the getPosition method through the prototype chain of Point. This also simulates the functionality of class, but it seems a lot more cumbersome and lacks static type detection. Therefore, classes are a very useful tool to master in TypeScript programming.

2. Class inheritance

Let’s look at inheritance as one of the three main object-oriented lines. In TypeScript, we use the extends keyword to define an abstract pattern for class inheritance:

class A {
  name: string;
  age: number;
  constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
  }
  getName() {
      return this.name; }}class B extends A {
  job: string;
  constructor(name: string, age: number) {
      super(name, age);
      this.job = "IT";
  }
  getJob() {
      return this.job;
  }
  getNameAndJob() {
      return super.getName() + this.job; }}var b = new B("Tom".20);
console.log(b.name);
console.log(b.age);
console.log(b.getName());
console.log(b.getJob());
console.log(b.getNameAndJob());
// Output: Tom, 20, Tom, IT, TomIT
Copy the code

As above, B inherits FROM A, then A is called A parent (superclass) and B is called A subclass (derived class). This is the most basic use of class inheritance. B is A derived class, derived from class A, where instances of B inherit the properties and methods of base class A. Therefore, instance B supports properties and methods such as name, age, and getName.

Note that a derived class that includes a constructor must call the super() method in the constructor, which is an important rule that TypeScript enforces. Constructors for derived classes must contain a ‘super’ call.

So how does this super() work? In fact, the super function calls the constructor of the base class. When we hover over the super method, we see A hint that the type is the constructor of base A: constructorA(name: string,age: number): A. It also specifies that two arguments need to be passed otherwise TypeScript will report an error.

Class modifiers

1. Access modifiers

In the definition of ES6 standard classes, by default, properties and methods defined in the instance are added to the instance after it is created; If the class defines a method that is not defined on this, the instance can inherit the method. If properties and methods defined with static modifiers are static properties and methods, the instance cannot access or inherit them.

Traditional object-oriented languages usually have access modifiers that control accessibility. There are three types of access modifiers in TypeScript:

  • Public: Modifies properties or methods that are public and visible anywhere;
  • Private: Modifies properties or methods that are visible only in the same class and are private;
  • Protected: Modifies properties or methods that are visible and protected only in the class itself and its subclasses.

(1) the public

Public means public, specifying properties and methods that are accessible through the instance after it is created, that is, externally to the class definition. The default is public, but TSLint may require qualifiers to indicate what type the property or method is:

class Point {
  public x: number;
  public y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
  public getPosition() {
    return ` (The ${this.x}.The ${this.y}) `; }}const point = new Point(1.2)
console.log(point.x)   / / 1
console.log(point.y)   / / 2
console.log(point.getPosition())  / / (1, 2)
Copy the code

(2) private

The private modifier means private. It modifies properties that are not accessible outside the class definition:

class Parent {
  private age: number;
  constructor(age: number) {
    this.age = age; }}const p = new Parent(18);

console.log(p);  // { age: 18 }
console.log(p.age); // error Property 'age' is private and only accessible within class 'Parent'.
console.log(Parent.age); // error Property 'age' does not exist on type 'typeof Parent'.

class Child extends Parent {
  constructor(age: number) {
    super(age);
    console.log(super.age); // Only public and protected methods of the base class are accessible via the 'super' keyword.}}Copy the code

The age property is a private property, and the compiler will raise an error when attempting to access the age property of p. The private property can only be accessed in the Parent class.

For super.age, super as an object means different things in different types of methods. Here we call super from constructor, which is equivalent to the parent class itself, using private modified properties that are not accessible in subclasses.

(3) protected

Protected is a protected modifier, similar to private, except that members of the protected modifier are accessible in subclasses that inherit the class. Replace the private modifier on the age property of the Parent class with the private modifier

class Parent {
  protected age: number;
  constructor(age: number) {
    this.age = age;
  }
  protected getAge() {
    return this.age; }}const p = new Parent(18);
console.log(p.age); // error Property 'age' is protected and only accessible within class 'Parent' and its subclasses.
console.log(Parent.age); // error Property 'age' does not exist on type 'typeof Parent'.
class Child extends Parent {
  constructor(age: number) {
    super(age);
    console.log(super.age); // error Only public and protected methods of the base class are accessible via the 'super' keyword.
    console.log(super.getAge()); }}new Child(18)
Copy the code

Protected can also be used to modify the constructor constructor. After adding the protected modifier, the class can no longer be used to create instances, but can only be inherited by subclasses. ES6 classes need to use new. For TS, just use the protected modifier:

class Parent {
  protected constructor() {
    //}}const p = new Parent(); // error Constructor of class 'Parent' is protected and only accessible within the class declaration.
class Child extends Parent {
  constructor() {
    super();
  }
}
const c = new Child();
Copy the code

2. Read-only modifiers

You can use the readonly keyword to set a property to read-only in a class:

class UserInfo {
  readonly name: string;
  constructor(name: string) {
    this.name = name; }}const user = new UserInfo("TypeScript");
user.name = "haha"; // error Cannot assign to 'name' because it is a read-only property
Copy the code

A property set to read-only. The instance can only read the value of this property, but cannot modify it.

Note that if a read-only modifier and a visibility modifier are present together, you need to write the read-only modifier after the visibility modifier.

Third, the type of class

1. Attribute type

(1) Parameter attributes

In the above examples, instance properties are initialized at the top of the class definition, received arguments from constructor and assigned to instance properties. You can use parameter properties to simplify this process. A parameter attribute precedes the constructor argument with an access qualifier:

class A {
  constructor(name: string){}}const a = new A("aaa");
console.log(a.name); // Attribute 'name' does not exist on error type 'A'

class B {
  constructor(public name: string){}}const b = new B("bbb");
console.log(b.name); // "bbb"
Copy the code

As you can see, when class B is defined, the constructor takes a single parameter, name, which is modified with the access modifier public. This declares the parameter attribute for name, and there is no need to explicitly initialize the attribute in the class.

(2) Static attributes

The static keyword is used in TypeScript as in ES6 to specify that a property or method is static. Instances do not add static properties or inherit static methods. You can use modifiers and the static keyword to specify an attribute or method:

class Parent {
  public static age: number = 18;
  public static getAge() {
    return Parent.age;
  }
  constructor() {
    //}}const p = new Parent();
console.log(p.age); // error Property 'age' is a static member of type 'Parent'
console.log(Parent.age); / / 18
Copy the code

If you use the private modifier, the same reasoning applies:

class Parent {
  public static getAge() {
    return Parent.age;
  }
  private static age: number = 18;
  constructor() {
    //}}const p = new Parent();
console.log(p.age); // error Property 'age' is a static member of type 'Parent'
console.log(Parent.age); // The error attribute "age" is private and can only be accessed in class "Parent".
Copy the code

(3) Optional class attributes

TypeScript also supports optional class attributes, which also use? To mark:

class Info {
  name: string; age? :number;
  constructor(name: string, age? :number.publicsex? :string) {
    this.name = name;
    this.age = age; }}const info1 = new Info("TypeScript");
const info2 = new Info("TypeScript".18);
const info3 = new Info("TypeScript".18."man");
Copy the code

2. Class type

The type of a class is similar to that of a function. When a class is declared, it also declares a special type. The name of the type is the class name, indicating the type of the instance of the class. When a class is defined, all declared property and method types other than constructors are members of this special type.

Once a class is defined and an instance is created, the instance is of the type of the class that created it:

class People {
  constructor(public name: string){}}let people: People = new People("TypeScript");
Copy the code

It is not necessary to specify p of type People when creating an instance; TS will infer its type. P < p style = “max-width: 100%; clear: both; min-height: 1em;

class Animal {
  constructor(public name: string){}}let people = new Animal("JavaScript");
Copy the code

So, if you want to implement the determination of the class that created the instance, you still need to use the instanceof keyword.

Four, the use of classes

1. An abstract class

Abstract classes are generally used to be inherited by other classes rather than directly used to create instances. Abstract classes and methods defined within classes, using the abstract keyword:

abstract class People {
  constructor(public name: string) {}
  abstract printName(): void;
}
class Man extends People {
  constructor(name: string) {
    super(name);
    this.name = name;
  }
  printName() {
    console.log(this.name); }}const m = new Man(); // error Expected 1 arguments, but got 0.
const man = new Man("TypeScript");
man.printName(); // 'TypeScript'
const p = new People("TypeScript"); // error Cannot create an instance of an abstract class.
Copy the code

Here we define an abstract class called People, in which the constructor method must pass in a string parameter and bind the name parameter value to the created instance. Use the abstract keyword to define an abstract method printName. This definition can specify parameters, specify parameter types, and specify return types. When instantiating a class directly from the abstract class People, an error is reported, and only a subclass can be created that extends from the abstract class People.

Look at the following example:

abstract class People {
  constructor(public name: string) {}
  abstract printName(): void;
}
class Man extends People {  // error Non-abstract class 'Man' does not implement inherited abstract member 'printName' from class 'People'
  constructor(name: string) {
    super(name);
    this.name = name; }}const m = new Man("TypeScript");
m.printName(); // error m.printName is not a function
Copy the code

As you can see, line 5 gives an error: the non-abstract class “Man” will not implement the abstract member “printName” inherited from “People”. An abstract method defined in an abstract class is not inherited in a subclass, so it must be implemented in a subclass.

The TypeScript abstract keyword marks not only classes and methods but also properties and accessors defined in a class:

abstract class People {
  abstract name: string;
  abstract get insideName() :string;
  abstract set insideName(value: string);
}
class Pp extends People {
  name: string;
  insideName: string;
}
Copy the code

Note: Neither abstract methods nor abstract accessors can contain actual code blocks.

2. Accessors

Accessor is the ES6 standard value storage function and value function, that is, when setting the property value of the function, and when accessing the property value of the function is called, the usage and writing is the same as ES6, getter, setter can intercept the read and write access to the class member:

class UserInfo {
  private name: string;
  constructor() {}
  get userName() {
    return this.name;
  }
  set userName(value) {
    console.log(`setter: ${value}`);
    this.name = value; }}const user = new UserInfo();
user.name = "TypeScript"; // "setter: TypeScript"
console.log(user.name); // "TypeScript"
Copy the code

Class interface

1. Class-type interfaces

Using interfaces you can force a class definition to include something:

interface FoodInterface {
  type: string;
}
class FoodClass implements FoodInterface {
  // error Property 'type' is missing in type 'FoodClass' but required in type 'FoodInterface'
  static type: string;
  constructor(){}}Copy the code

The above interface, FoodInterface, requires that the value used by this interface must have a type attribute. To define a class that uses the interface, the keyword implements is required. The implements keyword is used to specify which interface a class inherits from. Extends is used for interfaces, classes, and direct class inheritance, and implements is used for class inheritance interfaces.

The static attribute type is not added to the instance. The static attribute type is not added to the instance.

interface FoodInterface {
  type: string;
}
class FoodClass implements FoodInterface {
  constructor(public type: string){}}Copy the code

You can also use abstract classes:

abstract class FoodAbstractClass {
  abstract type: string;
}
class Food extends FoodAbstractClass {
  constructor(public type: string) {
    super();
  }
}
Copy the code

2. Interface inheritance classes

An interface can inherit from a class. When an interface inherits from that class, it inherits the members of that class, but not the implementation. That is, it inherits only the members and their types. An interface also inherits the private and protected members of a class. When an interface inherits a class that contains members modified by these two modifiers, the interface can only be implemented by that class or its subclasses:

class A {
  protected name: string;
}
interface I extends A {}
class B implements I {} // error Property 'name' is missing in type 'B' but required in type 'I'
class C implements I {
  // The error attribute "name" is protected, but type "C" is not A class derived from "A"
  name: string;
}
class D extends A implements I {
  getName() {
    return this.name; }}Copy the code

Sixth, other

1. Use class types in generics

Let’s start with an example:

const create = <T>(c: { new (): T }): T= > {
  return new c();
};
class Info {
  age: number;
}
create(Info).age;
create(Info).name; // Attribute 'name' does not exist on error type 'Info'
Copy the code

The create function takes a class as an argument and returns an instance of the class created. Note:

  • In the type definition of parameter C, new() represents the constructor calling the class, whose type is the type of the instance after the class is created.
  • Return new c() creates an instance of the passed class c and returns the instance type of the function’s return value.

So with this definition, TypeScript knows that when you call create, the values passed in and returned should be of the same class type.