Have you mastered these 10 knowledge points of TS?

Keywords: ts

Have you mastered these 10 knowledge points of TS?

  The day before yesterday

The following article comes from the way of cultivating immortals in the whole stack  , Author a Baoge

The whole stack is the way to cultivate immortals

Focus on sharing technical dry goods such as TS, Vue3, front-end architecture and source code analysis.

In object-oriented language, class is the construction of an object-oriented computer programming language, the blueprint for creating objects, and describes the common attributes and methods of the created objects.

1, Class properties and methods

1.1 member properties and static properties of class

In TypeScript, we can   class   Keyword to define a class:

class Person {
  name: string;               //  Member properties
  
  constructor(name: string) { //  Class constructor
    this.name = name;
  }
}

In the above code, we use   class   Keyword defines a   Person   Class that contains a   name   Member properties for. In fact, the class in TypeScript is a syntax sugar (the so-called syntax sugar changes a writing method on the basis of a previous syntax, and the functions are the same, but the writing methods are different, mainly to make it easier for developers to understand during use). If the compilation target is set to   ES5   The following code will be generated:

"use strict";
var Person = /** @class */ (function () {
    function Person(name) {
        this.name = name;
    }
    return Person;
}());

Class can not only define member properties, but also   static   Keyword to define static attributes:

class Person {
  static cid: string = "exe";
  name: string; //  Member properties
  
  constructor(name: string) { //  Class constructor
    this.name = name;
  }
}

So what is the difference between member attributes and static attributes? Before answering this question, let's take a look at the compiled   ES5   code:

"use strict";
var Person = /** @class */ (function () {
    function Person(name) {
      this.name = name;
    }
    Person.cid = "exe";
    return Person;
}());

From the above code, we can see that the member attribute is defined on the instance of the class, while the static attribute is defined on the constructor.

1.2 member methods and static methods of classes

In TS class, we can define not only member attributes and static attributes, but also member methods and static methods, as shown below:

class Person {
  static cid: string = "exe";
  name: string; //  Member properties
  
  static printCid() { //  Define static methods
    console.log(Person.cid);  
  }

  constructor(name: string) { //  Class constructor
    this.name = name;
  }

  say(words: string) :void { //  Define member methods
    console.log(`${this.name} says: ${words}`);  
  }
}

So what's the difference between member methods and static methods? Again, before answering this question, let's take a look at the compiled   ES5   code:

"use strict";
var Person = /** @class */ (function () {
    function Person(name) {
        this.name = name;
    }
    Person.printCid = function () {
        console.log(Person.cid);
    };
    Person.prototype.say = function (words) {
        console.log(this.name + " says\uFF1A" + words);
    };
    Person.cid = "exe";
    return Person;
}());

As can be seen from the above code, the member method will be added to the prototype object of the constructor, and the static method will be added to the constructor.

1.3 class member method overloading

Function overloading or method overloading is the ability to create multiple methods with the same name and different parameter numbers or types.   When defining the member method of a class, we can also overload the member method:

class Person {
  constructor(public name: string) {}

  say(): void; 
  say(words: string): void;
  say(words?: string) :void { //  Method overloading
    if(typeof words === "string") {
        console.log(`${this.name} says: ${words}`);  
    } else {
        console.log(`${this.name} says: Nothing`);  
    }
  }
}

let p1 = new Person("Semlinker");
p1.say();
p1.say("Hello TS");

If you want to learn more about function overloading, you can continue to read   It's time to show the real technology - TS separation   This is an article.

2, Accessor

In TypeScript, we can   getter   and   setter   Method to realize data encapsulation and validity verification to prevent abnormal data.

let passcode = "Hello TypeScript";

class Employee {
  private _fullName: string = "";

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (passcode && passcode == "Hello TypeScript") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}

let employee = new Employee();
employee.fullName = "Semlinker";

In the above code, for private  _ fullName   Property, we provide it externally   getter   and   setter   To control the access and modification of this property.

3, Class inheritance

Inheritance is a hierarchical model that connects classes. It refers to the ability of a class (called subclass or sub interface) to inherit the functions of another class (called parent class or parent interface) and add its own new functions. Inheritance is the most common relationship between classes or interfaces. Through class inheritance, we can realize code reuse.

Inheritance is a   is-a   Relationship:

In TypeScript, we can   extends   Keyword to implement class inheritance:

3.1 parent class

class Person {
  constructor(public name: string) {}

  public say(words: string) :void {
    console.log(`${this.name} says: ${words}`);  
  }
}

Subclass 3.2

class Developer extends Person {
  constructor(name: string) {
    super(name);
    this.say("Learn TypeScript")
  }
}

const p2 = new Developer("semlinker"); 
//  Output:   "semlinker says: Learn TypeScript"  

because   Developer   Class inherits   Person   Class, so we can   Developer   Call in the constructor of class   say   method. It should be noted that it is used in TypeScript   extends   Only a single class can be inherited when:

class Programmer {}

// Classes can only extend a single class.(1174)
class Developer extends Person, Programmer {
  constructor(name: string) {
    super(name);
    this.say("Learn TypeScript")
  }
}

Although only single inheritance is allowed in TypeScript, it allows us to implement multiple interfaces. Specific use examples are as follows:

interface CanSay {
   say(words: string) :void 
}

interface CanWalk {
  walk(): void;
}

class Person implements CanSay, CanWalk {
  constructor(public name: string) {}

  public say(words: string) :void {
    console.log(`${this.name} says: ${words}`);  
  }

  public walk(): void {
    console.log(`${this.name} walk with feet`);
  }
}

In addition, in addition to inheriting specific implementation classes, we can also inherit abstract classes when implementing inheritance.

4, Abstract class

use   abstract   The class declared by keyword is called abstract class. An abstract class cannot be instantiated because it contains one or more abstract methods.   The so-called abstract method refers to a method that does not contain a specific implementation:

abstract class Person {
  constructor(public name: string){}

  abstract say(words: string) :void;
}

// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error

Abstract classes cannot be instantiated directly. We can only instantiate subclasses that implement all abstract methods. The details are as follows:

class Developer extends Person {
  constructor(name: string) {
    super(name);
  }
  
  say(words: string): void {
    console.log(`${this.name} says ${words}`);
  }
}

const lolo = new Developer("lolo");
lolo.say("I love ts!"); //  Output: lolo says I love ts!

5, Class access modifier

In TS type, we can use   public,protected   or   private   To describe the visibility of such properties and methods.

5.1 public

public   Modified attributes or methods are public and can be accessed anywhere. By default, all attributes or methods are public:

class Person {
  constructor(public name: string) {}

  public say(words: string) :void {
    console.log(`${this.name} says: ${words}`);  
  }
}

5.2 protected

protected   Modified properties or methods are protected, which is similar to private, except that protected members can still be accessed in derived classes.

class Person {
  constructor(public name: string) {}

  public say(words: string) :void {
    console.log(`${this.name} says: ${words}`);  
  }

  protected getClassName() {
    return "Person";
  }
}

const p1 = new Person("lolo");
p1.say("Learn TypeScript"); // Ok
// Property 'getClassName' is protected and only accessible within class 'Person' and its subclasses.
p1.getClassName() // Error

It can be seen from the above error information that   protected   Modifier decorated methods can only be used in the current class or its subclasses.

class Developer extends Person {
  constructor(name: string) {
    super(name);
    console.log(`Base Class: ${this.getClassName()}`);
  }
}

const p2 = new Developer("semlinker"); //  Output: "Base Class: Person"  

5.3 private

private   The modified property or method is private and can only be accessed inside the class.

class Person {
  constructor(private id: number, public name: string) {}
}

const p1 = new Person(28, "lolo");
// Property 'id' is private and only accessible within class 'Person'.(2341)
p1.id // Error
p1.name // OK

It can be seen from the above error information that   private   The property modified by the modifier can only be accessed inside the current class. But is that true? In fact, this is just a hint from the TS type checker. We can still access it at runtime   Person   Instance   id   Properties. If you don't believe it, let's take a look at the compiled version   ES5   code:

"use strict";
var Person = /** @class */ (function () {
    function Person(id, name) {
      this.id = id;
      this.name = name;
    }
    return Person;
}());
var p1 = new Person(28, "lolo");

5.4 private fields

To solve the above problems, the TypeScript team has supported it since version 3.8   ECMAScript private field is used as follows:

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }
}

let semlinker = new Person("semlinker");
// Property '#name' is not accessible outside class 'Person' because it has a private identifier.
semlinker.#name // Error

that   ECMAScript private field   Follow   private   What's special about modifiers? Here, let's take a look at the generated by compilation   ES2015   code:

"use strict";
var __classPrivateFieldSet = //  Omit relevant codes
var _Person_name;
class Person {
  constructor(name) {
    _Person_name.set(this, void 0);
    __classPrivateFieldSet(this, _Person_name, name, "f");
  }
}

_Person_name = new WeakMap();
let semlinker = new Person("Semlinker");

From the above results, we can see that the newly added in ES2015 is used when processing private fields   WeakMap data type. If you don't know about WeakMap, you can read it   You don't know the WeakMap   This article. Let's summarize that private fields and general properties (even using   private   Modifier declared attributes) differ:

  • Private field with  #  Character, sometimes we call it private name;

  • Each private field name is uniquely limited to the class it contains;

  • TypeScript accessibility modifiers (such as public or private) cannot be used on private fields;

  • Private fields cannot be accessed outside the contained class or even detected.

6, Class expression

TypeScript 1.6 adds support for ES6 class expressions. Class expression is a syntax used to define a class. Like function expressions, class expressions can be named or anonymous. If it is a named class expression, the name can only be accessed inside the class body.

The syntax of class expression is as follows ([] square brackets indicate that it is optional):

const MyClass = class [className] [extends] {
  // class body
};

Based on the syntax of class expression, we can define a Point class:

let Point = class {
  constructor(public x: number, public y: number) {}
  public length() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }
}

let p = new Point(3, 4);
console.log(p.length()); //  Output: 5

It should be noted that when using class expressions to define classes, we can also use   extends   keyword. Space is limited, so I won't introduce it here. Interested partners can test it by themselves.

7, Generic class

Using generics in a class is also very simple. We just need to use after the class name  < T, ...>   The syntax of defines any number of type variables. Specific examples are as follows:

class Person<T> {
  constructor(
    public cid: T, 
    public name: string
  ) {}   
}

let p1 = new Person<number>(28, "Lolo");
let p2 = new Person<string>("exe", "Semlinker");

Next, we instantiate   p1   As an example, let's analyze the processing process:

  • In instantiation   Person   Object, we pass in   number   Type and corresponding construction parameters;

  • Later in   Person   Class, type variable   T   The value of becomes   number   Type;

  • Last constructor   cid   The parameter type will also become   number   Type.

I believe some readers will have questions here. When do we need to use generics? Generally, when deciding whether to use generics, we have the following two reference standards:

  • When your function, interface or class will handle multiple data types;

  • When a function, interface, or class uses the data type in multiple places.

8, Construct signature

In the TypeScript interface, you can use   new   Keyword to describe a constructor:

interface Point {
  new (x: number, y: number): Point;
}

In the above interfaces   new (x: number, y: number)   We call it constructive signature, and its syntax is as follows:

ConstructSignature:new TypeParametersopt ( ParameterListopt ) TypeAnnotationopt

In the above construction signature, TypeParametersopt  , ParameterListopt   and   TypeAnnotationopt   It represents optional type parameters, optional parameter list and optional type annotation respectively. So what's the use of understanding constructive signatures? Here's an example:

interface Point {
  new (x: number, y: number): Point;
  x: number;
  y: number;
}

class Point2D implements Point {
  readonly x: number;
  readonly y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const point: Point = new Point2D(1, 2); // Error

For the above code, the TypeScript compiler (v4.4.3) will prompt the following error message:

Type 'Point2D' is not assignable to type 'Point'.
Type 'Point2D' provides no match for the signature 'new (x: number, y: number): Point'.

To solve this problem, we need to   Point   Interface separation:

interface Point {
  x: number;
  y: number;
}

interface PointConstructor {
  new (x: number, y: number): Point;
}

After splitting the interface, in addition to the previously defined   Point2D   Class, we define another   newPoint   Factory function, which is used to   PointConstructor   Type to create the corresponding point object.

class Point2D implements Point {
  readonly x: number;
  readonly y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

function newPoint( // Factory method
  pointConstructor: PointConstructor,
  x: number,
  y: number
): Point {
  return new pointConstructor(x, y);
}

const point: Point = newPoint(Point2D, 1, 2);

9, Abstract construction signature

Abstract construction signature is introduced in typescript version 4.2 to solve the following problems:

type ConstructorFunction = new (...args: any[]) => any;

abstract class Utilities {}

// Type 'typeof Utilities' is not assignable to type 'ConstructorFunction'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
let UtilityClass: ConstructorFunction = Utilities; // Error.

From the above error information, we can not assign Abstract constructor types to non Abstract constructor types. To solve this problem, we need to use   abstract   Modifier:

declare type ConstructorFunction = abstract new (...args: any[]) => any;

It should be noted that for abstract constructor types, we can also pass in specific implementation classes:

declare type ConstructorFunction = abstract new (...args: any[]) => any;

abstract class Utilities {}
class UtilitiesConcrete extends Utilities {}

let UtilityClass: ConstructorFunction = Utilities; // Ok
let UtilityClass1: ConstructorFunction = UtilitiesConcrete; // Ok

For versions below TypeScript 4.2, we can solve the above problems in the following ways:

type Constructor<T> = Function & { prototype: T }

abstract class Utilities {}

class UtilitiesConcrete extends Utilities {}

let UtilityClass: Constructor<Utilities> = Utilities;
let UtilityClass1: Constructor<UtilitiesConcrete> = UtilitiesConcrete;

After introducing the abstract construction signature, let's briefly introduce it at last   Class type and typeof class type   The difference.

10, Class type and typeof class type

class Person {
  static cid: string = "exe";
  name: string; //  Member properties
  
  static printCid() { //  Define static methods
    console.log(Person.cid);  
  }

  constructor(name: string) { //  Class constructor
    this.name = name;
  }

  say(words: string) :void { //  Define member methods
    console.log(`${this.name} says: ${words}`);  
  }
}

// Property 'say' is missing in type 'typeof Person' but required in type 'Person'.
let p1: Person = Person; // Error
let p2: Person = new Person("Semlinker"); // Ok

// Type 'Person' is missing the following properties from type 'typeof Person': prototype, cid, printCid
let p3: typeof Person = new Person("Lolo"); // Error
let p4: typeof Person = Person; // Ok

By observing the above code, we can draw the following conclusions:

  • When used   Person   Class as a type, the value of the constraint variable must be   Person   Class;

  • When used   typeof Person   As a type, the value of a variable that can be constrained must contain static properties and methods on the class.

In addition, it should be noted that TypeScript uses   Structured   Type system, which is similar to that adopted by Java/C + +   Nominalization   The type system is different, so the following codes can work normally in TS:

class Person {
  constructor(public name: string) {}  
}

class SuperMan {
  constructor(public name: string) {}  
}

let p1: SuperMan = new Person("Semlinker"); // Ok

OK, in daily work, I'll introduce the common knowledge of TypeScript class here. Thank you for sharing!

 

Posted by codersrini on Wed, 24 Nov 2021 08:10:53 -0800