# Classes

TIP

Welcome to CLASS chapter!
TypeScript adds type annotations and other syntax to allow you to express relationships between classes and other types

Getters / Setters implements extends public protected private Static abstract

class Point {
  readonly name: string = "world";
  x: number;
  y: number;
}
 
const pt = new Point();
pt.x = 0;
pt.y = 0;

  • strictPropertyInitialization setting OFF/ON
    • controls whether class fields need to be initialized in the constructor
class GoodGreeter {
  name: string;
 
  constructor() {
    this.name = "hello"; // initialized
  }
}

# Super Calls

  • if you have a base class, you’ll need to call super() in your constructor body before using any this. members
class Base {
  k = 4;
}
class Derived extends Base {
  constructor() {
    super(); // before everything
    console.log(this.k);
  }
}

# Accessors

class C {
  _length = 0;
  get length() {
    return this._length;
  }
  set length(value) {
    this._length = value;
  }
  • some special inference rules
    • If get exists but no set, the property is automatically readonly
    • If the type of the setter parameter is not specified, it is inferred from the return type of the getter
    • Getters and setters must have the same Member Visibility

# Member Visibility

public protected private

  • control whether certain methods or properties are visible to code outside the class

  • public
    • default
    • can be accessed anywhere
    • you don’t ever need to write it on a class member, but might choose to do so for style/readability reasons( let's sb know exactly )

  • protected
    • only visible to subclasses of the class they’re declared in
    • we need to be careful to repeat the protected modifier if this exposure isn’t intentional.
class f {
  public greet(){}
  protected getName() {return "hi";}
}
class p extends f {
  public howdy() {
    console.log("Howdy, " + this.getName()); // from class f
  }
}
const g = new p();
g.greet(); // OK
g.getName(); // error
// ----
class Base {
  protected x: number = 1;
}
class Derived1 extends Base {
  protected x: number = 5;
}
class Derived2 extends Base {
  f1(other: Base) {
    other.x = 10; // error
  }
  f2(other: Derived1) {
    other.x = 10;  // error
  }
  f3(other: Derived2) {
    other.x = 10;
  }
}

  • private
    • doesn’t allow access to the member even from subclasses

  • some policy
    • private and protected are only enforced during type checking (means in js file thoes modifies aren't working ... )
    • private also allows access using ["bracket notation"] during type checking
    • If you need to protect values in your class from malicious actors, you should use mechanisms that offer hard runtime privacy, such as closures, WeakMaps, or private fields. Note that these added privacy checks during runtime could affect performance.

# Index Signatures

  • NOTE that it’s better to store indexed data in another place instead of on the class instance itself
class MyClass {
  [s: string]: boolean | ((s: string) => boolean);
 
  check(s: string) {
    return this[s] as boolean;
  }
}

# Class Heritage

implements extends super

  • Like other languages with object-oriented features, classes in JavaScript can inherit from base classes

  • implements
    • it's only a check that the class can be treated as the interface type. It doesn’t change the type of the class or its methods at all
    • implementing an interface with an optional property doesn’t create that property
interface A {
  x: number;
  y?: number;
  check(name: string): boolean;
}
class C implements A {
  x = 0;
  check(s) {
  return s.toLowercse() === "ok"; // Notice no error here
  }
}
const c = new C();
c.y = 10; // error

  • extends
    • Classes may extend from a base class. A derived class has all the properties and methods of its base class, and also define additional members
    • A derived class can also override a base class field or property, note that it not simply repeat a method but use super.
  • remember that JavaScript classes are a simple lookup object, looking up like a chain
class Base {
  greet() {
    console.log("Hello, world!");
  }
}
class Derived extends Base {
  greet(name?: string) { // name must optional
    if (name === undefined) {
      super.greet(); // override
    } else {
      console.log(`Hello, ${name.toUpperCase()}`);
    }
  }
}
 
const d = new Derived();
d.greet(); // Hello, world!
d.greet("reader"); // Hello, reader

# Static Members

  • NOTE: NO Static Classes, only members
  • static members aren’t associated with a particular instance of the class. They can be accessed through the class constructor object itself ( means you can access those fields without creating an instance of the class)
  • static members can also use the same public, protected, and private visibility modifiers
class MyClass {
  static x = 0;
  static printX() {
    console.log(MyClass.x);
  }
}
console.log(MyClass.x);
MyClass.printX();
  • Function properties like name, length, and call aren’t valid to define as static members
class S {
  static name = "S!"; // error
// Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'
}

  • static Blocks
    • we can write initialization code with all the capabilities of writing static Blocks
class Foo {
    static #count = 0;
    get count() {
        return Foo.#count;
    }
    static {
        try {
           ...
        }
        catch {}
    }
}

# Generic Classes

class Box<Type> {
  contents: Type;
  constructor(value: Type) {
    this.contents = value;
  }
}
//const b: Box<string>
const b = new Box("hello!");

# this

  • the value of this inside a function depends on how the function was called
class MyClass {
  name = "MyClass";
  getName() {
    return this.name;
  }
}
const c = new MyClass();
const obj = { // the function was called through the `obj` reference
  name: "obj",
  getName: c.getName, 
};
console.log(obj.getName()); // "ojb"
  • TypeScript provides some ways to mitigate or prevent this kind of error

  • Arrow Functions
    • when a function that will often be called in a way that loses its this context
class MyClass {
  name = "MyClass";
  getName = () => { // arrow func
    return this.name;
  };
}
const c = new MyClass();
console.log(c.getName()); // "MyClass"

  • this as parameters
    • an add a this parameter to method definitions to statically enforce that the method is called correctly
class MyClass {
  name = "MyClass";
  getName(this: MyClass) {
    return this.name;
  }
}
const c = new MyClass();
c.getName();// OK
const g = c.getName;
console.log(g());// Error

  • this as Types
    • a special type called this refers dynamically to the type of the current class
class Box {
  ...
  set(value: string) {
    ...
    return this;
  }
}

# Parameter Properties

public private protected readonly

  • special syntax for turning a constructor parameter into a class property with the same name and value
class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // No body necessary
  }
}
const a = new Params(1, 2, 3);
console.log(a.x);

# abstract Classes and Members

abstract

  • An abstract method or abstract field is one that hasn’t had an implementation provided. These members must exist inside an abstract class, which cannot be directly instantiated
abstract class Base {
  abstract getName(): string;
  printName() {
    console.log(this.getName())
  }
}
const b = new Base();// error
class t extends Base {
  getName() { //can't forget this
    return "world";
  }
}
const d = new t();
d.printName();// "world"

  • Sometimes you want to accept some class constructor function that produces an instance of a class which derives from some abstract class
abstract class Base { 
  abstract getName(): string;
  printName() {}
}
class Derived extends Base {
  getName() {
    return "";
  }
}
// ---cut---
function greet(ctor: new () => Base) {
  const instance = new ctor();
  instance.printName();
}
greet(Derived);// it's concrete
greet(Base); //error, because it's abstract
Last Updated: 2021/11/18 下午3:32:56