The original TS class has so much detail —-TypeScript series :(5) Classes

Classes are available in JavaScript in the ES2015 version. TS has fully supported classes and added some additional syntax to enhance their expression. In this article, we’ll take a look at all you know and don’t know about classes in TS.

Class members

1. Property Fields (Fields)

The field declaration creates a common writable instance attribute for the class addition. We can add a type comment to the field, if not, it will be of type any, which we don’t want to happen.

class Person { name:string; age: number; gender; // gender is any}Copy the code

Fields can be declared with an initial value, whose type is automatically inferred by TS, and the value is initialized automatically when instantiated.

Class Person {name = 'cc' // name = string age = 18 // age = string gender: 1 | 2 = 2 / / without annotation types, gender will be concluded for number} const cc = new Person (cc). The name / / 'cc' cc. The age / / 18Copy the code

If open the strict property initialization check: strictPropertyInitialization, there is no initialization field must be initialized in the constructor, cannot be initialized in the other method, TS will not go to detect other initialization method.

Class Person {name = 'cc' // name = string constructor(){this.age = 18}}Copy the code

In fact, this detection is turned on to prevent unexpected errors caused by null property values. We can use non-null assertions to make it clear that the property will not be empty, and so no errors will be reported.

class Person { name! : number // non-empty assertion}Copy the code

2. readonlyRead-only property

Properties that add the Readonly modifier do not allow reassignment outside the constructor.

class Person { readonly name: Constructor (){this.name = 'yy'} setName(){this.name = 'hi' // error Const cc = new Person() cc.name = 'cc' // Error, read-only attributes do not allow reassignmentCopy the code

3. The constructor function

The constructor receives the parameters passed in at instantiation and can provide default values for the parameters. Initializes class instances in constructors, assigning attribute values, calling class methods, and so on.

class Person {
  name: string;
  constructor(name = 'cc'){
    this.name = name
  }
}
Copy the code

Remember how we talked about function overloading in the Typescript series: functions? Constructors can also be overloaded, of course. Note that the constructor’s overloaded signature and implementation signature have no return value type.

class Person { name: string; Constructor (name: number) constructor(name: string, age: number) number | string, age? : number){ // ... }}Copy the code

4. Callsuper( )

We know that classes can inherit from a base class through the extends keyword. In this case, we need to call super() before using this keyword in the constructor, which is equivalent to calling the constructor of the parent class.

class Person { name: string; } // Error: this class User1 extends Person {constructor(name:) without calling super() first Class User2 extends Person {constructor(name: string){super()}}Copy the code

5. The methods method

The functions inside a class are called methods. You do not need the function keyword to declare a method.

class Person { name: string; Constructor (name = 'cc'){this.name = name} // constructor(name: string):void {this.name = name}}Copy the code

6. Access setters/getters

It’s no different from JS.

class Person {
  _name: string;
  constructor(name = 'cc'){
    this._name = name
  }
  // setter
  set name(name: string):void {
    this._name = name
  }
  get name(): string{
    return this._name
  }
}
Copy the code

TS has a few special corollary to accessors:

  • If there is a GET but no set, the property is inferred as readonly.
  • If the setter does not specify the type of the argument, it is inferred to be the type of the return value of the getter.
  • Getters and setters have the same visibility.

7. Index signature

Classes can also use index signatures, similar to those used in object types.

class Person { [x: string]: string | number | ((s? : string) => string | number) name = 1 getName() { return this.name } }Copy the code

Class inheritance

1. Implement the statement

Use the IMPLEMENTS statement to check whether a class conforms to an interface specification. To implement an interface, the class needs to contain all the attributes and methods of the interface to pass detection.

interface Person { name: string setName: (x: Class People implements Person {name: string = 'cc' setName(name: string); string) { this.name = name }; }Copy the code

Multiple interfaces can be implemented simultaneously:

interface Person { name: string setName: (x: string) => void } interface Manager { id: Class People implements Person, Manager {name: string = 'cc' id: string = '001' setName(name: string) { this.name = name }; }Copy the code

Note that the IMPLEMENTS statement simply tests whether the class conforms to the interface specification.

2. The road

  • The extends statement lets a class inherit from a base class, acquire all of its properties and methods, and define its own properties and methods.
class Person {
  name: string = 'cc'
}

class Manager extends Person {

}

let cc = new Manager()
cc.name  // 'cc'
Copy the code
  • Override the superclass method, which can be called with super.xx(). The subclass’s methods need to be compatible with the parent class’s methods, including the number of arguments, types, and return values.
class Person { name: string = 'cc' setName(name: string){ this.name = name } } class Manager extends Person { setName(name: string | number){ if(typeof name === 'number'){ this.name = String(name + 100) }else{ super.setName() } } } let cc = new  Manager() cc.setName(99) cc.name // '199'Copy the code
  • Field type declaration

Initialization of the subclass begins after the constructor of the parent class completes, possibly overwriting properties or methods from the parent class. This process wastes performance when an attribute of a subclass is a subtype of the corresponding attribute of the parent class. Field types can be declared using the DECLARE keyword to make them independent of runtime effects.

interface Animal { name: string } interface Dog extends Animal { bark:() => void } class AnimalHouse { resident: Animal constructor(animal: Animal){this.resident = Animal}} class DogHouse extends AnimalHouse {// Declare keyword, Make the resident property type fixed to Dog declare resident: Dog constructor(Dog: Dog){// Don't forget to call super() super()}}Copy the code
  • Initialization sequence

Parent field initialization –> Parent constructor execution –> Subclass field initialization –> Subclass constructor execution

3. Inherit built-in types

Inherits built-in types, such as Array, Error, etc. When super() is called in the constructor, the prototype of this points to the caller that mistakenly points to super, i.e., Array, Error, etc. Built-in types. ES6 uses new.target to adjust the prototype chain, but the value of new.target is not guaranteed in ES5. Therefore, after calling super(), we manually adjust the stereotype chain so that the stereotype of this points to our new class. * Object.setPrototypeof () * is the method to use (those that don’t support this method can take a step back and use Object.prototype.proto).

class MsgError1 extends Error { naame = 123 constructor(m: string) { super(m); } sayHello() { return "hello " + this.message; } // let m1 = new MsgError1('cc') // sayHello is on MsgError because of the prototype chain Error, Class MsgError2 extends Error {naame = 123 constructor(m: string) {super(m); Object.setPrototypeOf(this, MsgError2.prototype) } sayHello() { return "hello " + this.message; }} let m2 = new MsgError2('cc') m2.sayHello(Copy the code

Note that this problem will be passed on, meaning that subclasses created from MsgError2 will again need to manually redirect the prototype. In addition, IE10 and lower versions are not supported.

Member Visibility

In TS, modifiers such as public, protected, and private are implemented to make members visible.

1. public

The public modifier is used to define public members. This is also the default member visibility. When no visibility modifier is written, it defaults to public. Members that are declared public and can be accessed anywhere. It’s too easy to give chestnuts.

2. protected

Protected members can only be accessed from the class or its subclasses, not from the instance.

class Person { protected name: string constructor(name: String){this.name = name} getName(){return this.name}} const cc = new Person('cc') cc.name Instances cannot access protected membersCopy the code

In a subclass, if we redeclare a protected member of the base class via a field, we will make it a public member in the subclass, unless the protected modifier is added:

class Person { protected name: string protected age: Number} class Manager extends Person {// Without the protected modifier, name becomes public. Protected age: number constructor(name: string, age) Number){super() this.name = name this.age = age}} const cc = new Manager('cc', 18) cc.name // 'cc' // error, Instance cannot call protected cc.ageCopy the code

3. private

Members decorated by private can only be accessed within a class, not by instance, or in its subclasses.

Class Person {private name: string} class Manager extends Person {// Error, private member cannot access name in subclass: string constructor(name: string, age: Name = name}} const cc = new Person('cc', 18) cc.name // also error, Private members cannot be accessed by instanceCopy the code

But TS supports fetching a private member on an instance of a class from another instance of that class:

class Person { private name: string constructor(name: string){ this.name = name } hasSameName(other: Name return this.name === other.name}} const cc = new Person('cc') const yy = new Person('yy') cc. HasSameName (yyCopy the code

Note that member visibility is only valid for type checking of TS. Once the code has been compiled into JS code, members that were pretected or private in TS can be viewed through class instances in JS. In addition, the JS private modifier “#” can be implemented to remain a private member after compilation. Therefore, if you want to protect members through privatization, you should use closures, WeakMap, or private field “#”.

The static member is static

First, let’s make it clear that a class is itself an object. We use the static modifier to make a member static. Static members have nothing to do with the instance of the class, but are attached to the class object itself, which can have the same name as the instance member, and the this in the static method refers to the class object itself, through which we access the class member.

Class Person {// This is the instance member. String){this._name = name} // Static attribute name static _name: String = 'person' // Static method where this refers to class person static setName(name: string){ this._name = name console.log(this) // class Person { // ... }}} const cc = new Person('cc') // access instance member _name cc._name // 'cc'Copy the code

You may wonder why I use _name instead of name, but it’s not that I don’t use it, it’s that I can’t use it. You’ll get the answer later.

Static members can also use public, protected, private, and other modifiers. Similarly, protected static properties can only be accessed by static members of a class or subclass; Private static members can only be accessed by static members of a class.

Class Person {protected static _name = 'Person'} person._name // error: protected member cannot be accessedCopy the code

Static members can be inherited by subclasses:

class Person {
  static _name = 'person'
  static setName(name: string){
    this._name = name
  }
}

class Manager extends Person {

}

Manager._name  // 'person'
Manager.setName('cc')
Manager._name  // 'cc'
Copy the code

Special static name: name. Because of the built-in static attribute function. name, we cannot use name when naming static attributes, otherwise there will be conflicts.

Class Person {// error: static name = 'Person'}Copy the code

Static domain

I call static blocks of the class static fields, with *static {} * declaring an area where statements written can be executed automatically and have access to private attributes like “#name”. Therefore, static members can be written in static fields for initialization logic. Here can not think of what good chestnut, on the handling of the official website:

class Foo {
  static #count = 0;

  get count() 
    return Foo.#count;
  }
  static {
    try {
      const lastInstances = loadLastInstances();
      Foo.#count += lastInstances.length;
    }
    catch {}
  }
}
Copy the code

6. Generic classes

When the new operation is performed, the type parameters of the generic class are also inferred from the parameters passed in.

class Person<T> { name: T constructor(name: T){ this.name = name } setName(name: T){this.name = name}} const cc = new Person('cc') // T is inferredCopy the code

Generic classes can impose generic constraints and specify default values for type parameters just like generic interfaces. Everyone gets the point, right? No chestnuts.

Static members cannot enjoy generics:

Class Person<T> {// error, static member cannot reference type parameter static _name: T}Copy the code

This is because there is only one static member, while instance members exist on each instance. If static members can enjoy generics, we will create an instance of a, pass in type string, and the static attribute _name will be of type string. If we create an instance b and pass in type number, what is the type of the static attribute _name? String or number? Obviously none of them make sense. So static members cannot use type parameters.

This at runtime

The this reference in Ts is consistent with JS, so sometimes we need to prevent members from losing this context.

1. Use arrow functions

class Person {
  name: string
  setName = (name: string) => {
    this.name = name
  }
}
Copy the code

But there are trade-offs:

  • Doing so ensures that the this of the setName method always points correctly to the instance itself;
  • Methods defined in this way in use are not mounted to prototypes, but are added to each instance and therefore take up more memory;
  • Similarly, its subclass cannot call its parent’s setName method with super,setName, because it cannot be found on the prototype chain;

2. Use this

Just as we use this as an argument to a TS function, we can do the same in a class method.

Class Person {name: string} // If this is of type Person, only Person instances can be called. string){ this.name = name } }Copy the code

The only drawback is that students who are used to JS thinking might try to call this method from other objects, which obviously won’t work.

Eight,Use this as the type

First of all, it’s very useful. In TS classes, this can be a special type that is dynamically inferred by the current class.

Class Person {name: string} // Use this as the type of parameter Person. This){this.name = person.name return this}} const a = new person () const b = a.setName(a) // b is of the current class personCopy the code

The setName here returns this, which is the instance value, whose type is inferred to be this, which is the type. This type is dynamically inferred to the current class when setName is called. The advantage of this is that subclasses can also be automatically inferred to subclasses. For example, if we call setName in an instance of the Person class, the return value will be Person; If setName is called from an instance of Person’s subclass Manager, the return type is Person’s subclass Manager:

Class Person {name: string} // Use this as the type of parameter Person. this){ this.name = person.name return this } } class Manager extends Person { } const y = new Person() const yy = Y.setname (y) // Person const c = new Manager() const cc = c.setname (c) // The type of cc is ManagerCopy the code

Type guarding based on this: As in functions, we can use this is Type at the Type of the return value of a method on a class or interface for Type reduction. Write the simplest chestnut, it is lazy to commit QWQ.

class Person { name? = 'cc'} class Manager extends Person {isPerson()this isPerson {return this refers to the class instance return HasName ()this is {name: string} {return this.name! Const cc = new Person() if(cc.hasname ()){//... }Copy the code

9. Parameter attributes

Parameter properties are a very convenient syntax provided by TS. Constructor arguments can be changed from ordinary arguments to parameter attributes by prefixing them with modifiers such as public, protected, private, or readonly. Parameter properties are both constructor parameters and are automatically added to the instance as instance properties, and are assigned automatically when passing parameters, without the need for assignment inside the function.

class Person { constructor(public name: string, protected age: number, private gender: 1 | 2) {/ / do not need to be here and then to assign} setAge (age: number){ this.age = age } getAge(){ return this.age } } const cc = new Person('cc', 18, 2) cc.name // 'cc' cc.setage (20) cc.getage () // 20 cc.gender // error: private attribute cannot pass instanceCopy the code

Class expression

This is the same as the function expression:

Const Person = class {name: string = 'cc'} const cc = new Person() cc.name // 'ccCopy the code

Eleven,abstractAbstract classes and their members

Classes that contain abstract members are abstract classes. Both abstract classes and abstract members need to be preceded by the abstract modifier. Abstract classes cannot be instantiated with New. Instead, they are used as base classes that declare abstract methods or properties, and their subclasses need to implement all of these methods or properties.

abstract class Person { abstract name: string; abstract setName(x: string): void; } class Manager extends Person {name: string = 'Manager' setName(name: string){this.name = name}}Copy the code

Relationships between class members

Like other types, classes are compared by structure. When they have the same members, they can replace each other. When A class A contains all the members of another class B, class A is considered A subclass of class B, even though there is no explicit extends inheritance.

class Person { name: string; } class Manager { name: string; Age: number} const person: person = new Manager()Copy the code

This looks intuitive and simple, but in rare cases it can look emMM and weird. Bring an official chestnut:

Class Empty {} function fn(x: Empty) {// do nothing} fn({}); fn(fn); fn(0); fn(undefined)Copy the code

Class knowledge is shared here, next share TS module content, be there or be square!