Key Concepts of TS - CLASSES

2023-05-17 19:06:36 浏览数 (2)

# CLASSES

代码语言:javascript复制
class Sampler {
  greeting: string;
  constructor (message: string) {
    this.greeting = message;
  }
  greet () {
    return "Hello, "   this.greeting;
  }
}

let sampler = new Sampler("world");

console.log(sampler.greet()); // Hello, world

# Inheritance

Inheritance is another paradigm that is one of the cornerstones of object-oriented programming.Inheritance means that an object uses another object as its base type, thereby inheriting all the characteristics of the base object, including all the properties and functions.

Both interfaces and classes can use inheritance.

代码语言:javascript复制
class Animal {
  name: string;
  constructor (theName: string) {
    this.name = theName;
  }
  move (distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Frog extends Animal {
  constructor (name: string) {
    super(name);
  }
  move (distanceInMeters = 5) {
    console.log("Jumping...");
    super.move(distanceInMeters);
  }
}

class Horse extends Animal {
  constructor (name: string) {
    super(name);
  }
  move (distanceInMeters = 45) {
    console.log("Galloping...");
    super.move(distanceInMeters);
  }
}

let jack = new Frog("Froggy the Traveller");
let nick: Animal = new Horse("MrHorse");

jack.move();
nick.move(34);

Derived classes containing constructor functions must call super(), which will execute the constructor function of the base class.

# Interface Inheritance

代码语言:javascript复制
interface IBase {
  id: number | undefined;
}

interface IDerivedFromBase extends IBase {
  name: string | undefined;
}

class InterfaceInheritance implements IDerivedFromBase {
  id: number | undefined;
  name: string | undefined;
}

Although we only have the properties shown in this example, the same rules apply for functions.

# Class Inheritance

代码语言:javascript复制
class BaseClass implements IBase {
  id: number | undefined;
}

class DerivedFromBaseClass extends BaseClass implements IDerivedFromBase {
  name: string | undefined;
}

The second class, DerivedFromBaseClass, not only inherits from the BaseClass class (using the extends keyword) but also implements the IDerivedFromBase interface. Since BaseClass already defines the id property required in the IDerivedFromBase interface, the only other property that the DerivedFromBaseClass class must implement is the name property. Therefore, we need to include the definition of only this property in the DerivedFromBaseClass class.

TS does not support the concept of multiple inheritance. Multiple inheritance means that a single class can be derived from multiple base classes. TS supports only single inheritance, and therefore any class can have only one base class.

However, a class can implement many interfaces:

代码语言:javascript复制
interface IFirstInterface {
  id: number | undefined;
}
interface ISecondInterface {
  name: string | undefined;
}

class MultipleInterfaces implements IFirstInterface, ISecondInterface {
  id: number | undefined;
  name: string | undefined;
}

This means that the MultipleInterfaces class must implement the id property to satisfy the IFirstInterface interface, and the name property to satisfy the IFirstInterface interface.

# Access Modifiers

Access modifiers allow you to hide the state of an object from external access and control access to this state. TS has three modifiers: public , protected , and private.

# Public by Default

In TS, each class member will be public by default. But we can mark the members of the class public explicitly.

代码语言:javascript复制
class Animal {
  public name: string;
  public constructor(theName: string) {
    this.name = theName;
  }
  public move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

# Private Modifier

When a class member is marked with the private modifier, it cannot be accessed outside of that class.

代码语言:javascript复制
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name; // Error: 'name' is private;

TS is a structured type system. When we compare two different types, regardless of where and how they are described and implemented, if the types of all their members are compatible, it can be argued that the types themselves are compatible.

However, when comparing types with the private access modifier, this happens differently. Two types will be considered compatible if both members have the private modifier from the same declaration. This also applies to protected members.

代码语言:javascript复制
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

class Cat extends Animal {
  constructor () {
    super("Cat");
  }
}

class Employee {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

let animal = new Animal("Goat");
let cat = new Cat();
let employee = new Employee("Bob");

animal = cat;
// animal = employee; // Error: Animal and Employee are not compatible
// Types have separate declarations of a private property 'name'.

Even though Employee has a private member named name, this is not the member we declared in Animal.

# Protected Modifier

The protected modifier acts similarly to private, except that members declared by protected can be accessed in subclasses.

代码语言:javascript复制
class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;
  constructor (name: string, department: string) {
    super(name);
    this.department = department;
  }
  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch()); // "Hello, my name is Howard and I work in Sales."
// console.log(howard.name); // error

The constructor can also have the protected modifier.This means that a class cannot be created outside of the class that contains it, but it can be inherited.

代码语言:javascript复制
class Person {
  protected name: string;
  protected constructor(theName: string) {
    this.name = theName;
  }
}

class Employee extends Person {
  private department: string;
  constructor (name: string, department: string) {
    super(name);
    this.department = department;
  }
  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
// let john = new Person("John"); // Error: The 'Person' constructor is protected
// Constructor of class 'Person' is protected and only accessible within the class declaration.

# Readonly Modifier

You can make properties read-only by using the readonly keyword. Readonly properties must be initialized when they are declared or in the constructor.

代码语言:javascript复制
class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor (theName: string) {
    this.name = theName;
  }
}
let dad = new Octopus('Man with the 8 strong legs');
// dad.name = 'Man with the 3-piece suit'; // Cannot assign to 'name' because it is a read-only property.

# Type Iterator Modifier

By using the optional sign along with the type modifiers, we can create more explicit and readable type declarations. We can also use the -(minus) sign to remove optional declarations from the ? properties.

代码语言:javascript复制
interface ICar {
  name: string;
  age: number;
}

type ReadonlyCar = {
  readonly [K in keyof ICar] : ICar[K];
}

Type iterator modifiers make it easy to extend existing types and apply massive changes to all their properties.

代码语言:javascript复制
const car: ICar = {
  name: 'Mercedes',
  age: 2
};

const readOnlyCar: ReadonlyCar = {
  name: 'BMW',
  age: 5
};

car.age = 3;
// readOnlyCar.age = 6; // Error: Cannot assign to 'age' because it is a read-only property.

We can specify that all properties are optional via ?.

代码语言:javascript复制
type ReadonlyCar = {
  readonly [K in keyof ICar]?: ICar[K];
};

Also, we can specify that all properties are strings, or make each property as a union of their original type and string through a vertical bar |.

代码语言:javascript复制
type ReadonlyCar = {
  readonly [K in keyof ICar]?: ICar[K] | string;
};

We can remove the flag not only with ? sign. Starting with TS 2.8, it became possible to add a minus sign (-) before the character that we want to remove.

代码语言:javascript复制
type ReadonlyCar = {
  readonly [K in keyof ICar]-?: ICar[K];
};

Since we have the flexibility with the - sign to remove flags from our types, the sign has also been added to this feature. We can say more clearly what we are adding and what we are removing.

代码语言:javascript复制
type ReadonlyCar = {
   readonly [K in keyof ICar] -?: ICar[K];
};

Type iterator modifiers are useful if:

  • there is an interface that cannot be changed directly(for example, from the library);
  • there is an interface that we want to continue using for some purposes, and create a small variation of it (using modifiers) for use for other purposes;
  • in both cases, the type iterator modifiers “follow” the form of the original interface;

# Parameter Properties

The parameter properties allow you to create and initialize members in one place.

代码语言:javascript复制
class Octopus {
  readonly numberOfLegs: number = 8;
  constructor (readonly name: string) {
  }
}

Parameter properties are declared before a constructor parameter that has an availability modifier, readonly, or both. Using the private parameter property declares and initializes the private member; so do public, protected, and readonly.

# Accessors (Getters / Setters)

TS supports getters and setters as a way to intercept accesses to object properties. This gives you more control over the moment you interact with the properties of objects.

代码语言:javascript复制
class Employee {
  fullName: string;
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
  console.log(employee.fullName);
}

Allowing fullName to be set directly is quite convenient, but it can lead to problems if someone wants to change the name at will.

代码语言:javascript复制
let password = 'secret password';
class Employee {
  private _fullName: string;
  get fullName(): string {
    return this._fullName;
  }
  set fullName(newName: string) {
    if (password && password == 'secret password') {
      this._fullName = newName;
    } else {
      console.log('Error: Unauthorized update of employee!');
    }
  }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
  console.log(employee.fullName);
}

# Static Properties

We can also create static class members, those that are visible in the class without creating an instance.

代码语言:javascript复制
class Grid {
  static origin = {x: 0, y: 0};
  calculateDistanceFromOrigin(point: {x: number; y: number;}) {
    let xDist = (point.x - Grid.origin.x);
    let yDist = (point.y - Grid.origin.y);
    return Math.sqrt(xDist * xDist   yDist * yDist) / this.scale;
  }
  constructor (public scale: number) {
  }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

# Abstract Classes

Abstract classes are base classes from which others inherit. Their instances cannot be created directly. Unlikean interface, an abstract class can contain the implementation details of its members. The abstract keyword is used to define abstract classes, as well as abstract methods within such classes.

代码语言:javascript复制
abstract class Animal {
  abstract makeSound(): void;
  move(): void {
    console.log('roaming the earth...');
  }
}

Methods within an abstract class that are marked as abstract do not contain an implementation and must beimplemented in derived classes.

代码语言:javascript复制
abstract class Department {
  constructor(public name: string) {
  }
  printName(): void {
    console.log('Department name: '   this.name);
  }
  abstract printMeeting(): void; // must be implemented in derived classes
}

class AccountingDepartment extends Department {
  constructor() {
    super('Accounting and Auditing'); // constructors in derived classes must call super()
  }
  printMeeting(): void {
    console.log('The Accounting Department meets each Monday at 10am.');
  }
  generateReports(): void {
    console.log('Generating accounting reports...');
  }
}

let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName(); // ok to call an abstract method
department.printMeeting(); // ok to call a concrete method
department.generateReports(); // error: method doesn't exist on declared abstract type

# Constructors

When you declare a class in TS, you are actually creating multiple declarations at the same time.

The first declaration is of the class instance type.

代码语言:javascript复制
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, "   this.greeting;
  }
}
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // Hello, world

We also create another value, which is called a constructor function. This function is called when we create instances of the class using new. To see how this looks in practice, let’s look at the JavaScript code generated by the compiler from the example above:

代码语言:javascript复制
let Greeter = (function() {
  function Greeter(message) {
    this.greeting = message;
  }
  Greeter.prototype.greet = function() {
    return "Hello, "   this.greeting;
  };
  return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // Hello, world

Another way to think about each class is: there is an instance part and a static part.

代码语言:javascript复制
class Greeter {
  static standardGreeting = "Hello, there";
  greeting: string;
  greet() {
    if (this.greeting) {
      return "Hello, "   this.greeting;
    }
    else {
      return Greeter.standardGreeting;
    }
  }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet()); // Hello, there

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet()); // Hey there!

# Using a Class as an Interface

As we discussed in the previous section, a class declaration creates two things: a type that describes instances of the class, and a constructor function. Since classes create types, we can use them in the same way as interfaces.

代码语言:javascript复制
class Point {
  x: number;
  y: number;
}
interface Point3d extends Point {
  z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

0 人点赞