Ts中的Class

TypeScript offers full support for the class keyword introduced in ES2015.

定义类

class Point {
    // 属性默认是公共的,可以在类外部改变
    // 应该显式指定类型或可以推断的类型
    x:number = 10;
    // 如果开启了--strictPropertyInitialization,如果没有初始化,必须在constructor中初始化
    y:number;
    z = 10
    //name: string; 
    //Property 'name' has no initializer and is not definitely assigned in the constructor.
    constructor(){
        console.log('Point constructor')
        this.y = 10
    }
}
const p = new Point();
p.x = 20
console.log(p.x,p.y,p.z)


//输出结果如下:
[LOG]: "Point constructor" 
[LOG]: 20,  10,  10 


//上述typescript编译为ES6后,如下:
class Point {
    constructor() {
        this.x = 10;
        this.y = 10;
        this.z = 10;
        console.log('Point constructor');
    }
}
const p = new Point();
console.log(p.x, p.y, p.z);

定义类表达式

和ES6中一样,typescript中也支持类表达式:

const Point =  class {
    x:number = 10;
    y:number = 10;
    z = 10
    constructor(){
        console.log('Point constructor')
    }
}
const p = new Point();
console.log(p.x,p.y,p.z)

// 编译为ES6如下:
const Point = class {
    constructor() {
        this.x = 10;
        this.y = 10;
        this.z = 10;
        console.log('Point constructor');
    }
};
const p = new Point();
console.log(p.x, p.y, p.z);

断言操作符!

如果一个属性,不希望通过构造函数初始化,可以使用!操作符

class OKGreeter {
  // Not initialized, but no error
  name!: string;
}

readonly Fields

通过readonly修饰的字段属性,只能在constructor中进行初始化。

class Greeter {
  readonly name: string = "world";
  constructor() {
    this.name = "hello"
  }

  err() {
    // 无法赋值
    //this.name = "not ok";
  }
}
const g = new Greeter();
// g.name = "also not ok"; //无法赋值

Constructors

类的构造函数和函数很类似,可以添加参数,添加类型,给默认值,或进行重载(overloads)

class Point {
  x: number;
  y: number;

  // Normal signature with defaults
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

class Point {
  // Overloads
  constructor(x: number, y: string);
  constructor(s: string);
  constructor(xs: any, y?: any) {
    // TBD
  }
}

:zap:构造函数与函数的区别:

  • Constructors can’t have type parameters - these belong on the outer class declaration, which we’ll learn about later
  • Constructors can’t have return type annotations - the class instance type is always what’s returned

:100:Super Calls

class Base {
  k = 4;
}

class Derived extends Base {
  constructor() {
    super()
    // 如果需要访问this. 必须先调用super()
    console.log(this.k);
  }
}
new Derived()  // 4

methods

类中的函数属性称为方法(method)。

class Point {
  x = 10;
  y = 10;

  scale(n: number): void {
    this.x *= n;
    this.y *= n;
  }
}

Getters / Setters

class C {
  private _length = 0;
  get length() {
    return this._length;
  }
  set length(value) {
    this._length = value;
  }
}
const c = new C()
// c._length = 10 // private 修饰的只能在类内部访问
c.length = 100
console.log(c.length)  //100

:zap: ​注意:

  • 只要有get属性,属性就是只读属性(readonly)
  • set的参数类型可以省略,根据get的返回类型进行推断
  • setter/getter属性的可见性应该一样(public、private、protected)

Index Signatures

:zap: 不是很懂

class MyClass {
  [s: string]: boolean | ((s: string) => boolean);

  check(s: string) {
    return this[s] as boolean;
  }
}

implements 与 extends

implements

类可以实现接口,以检查类是否满足特定要求:

interface Pingable {
  name: string;
  ping(): void;
}

class Sonar implements Pingable {
    //必须实现接口中的属性
    name: string = 'PingPang';
    // constructor(public name:string){} 
    //必须实现接口中的方法
    ping(){
        
    }
}

当然,一个类也可以同时实现多个接口:class C implements A, B {}

需要注意的是:

implementing an interface with an optional property doesn’t create that property:

interface A {
  x: number;
  y?: number;
}
class C implements A {
  x = 0;
}
const c = new C();

//c.y = 10;	 //error:Property 'y' does not exist on type 'C'.

extends

类(子类/派生类)可以通过extends关键字继承另一个类(基类)。

这样子类就有了基类中所有的属性和方法,同时子类还可以定义自己额外的属性和方法。

class Animal {
  move() {
    console.log("Moving along!");
  }
}

class Dog extends Animal {
  woof(times: number) {
    for (let i = 0; i < times; i++) {
      console.log("woof!");
    }
  }
}

const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);

派生类可以覆写基类的方法和属性,也可以通过super.语法调用基类的==方法==。

class Base {
  name = 'BaseName'
  greet() {
    console.log(`Hello,Base !`);
  }
}

class Derived extends Base {
  name = 'DerivedName'
  greet(name?: string) {
    if (name === undefined) {
      //可以访问基类的方法
      super.greet();
    } else {
      // 无法访问基类的属性  
      console.log(`super name -${super.name}`)
      console.log(`Hello, ${name}`);
    }
  }
}

const d = new Derived();
d.greet();
d.greet(d.name);

//输出:
[LOG]: "Hello,Base !" 
[LOG]: "super name -undefined" 
[LOG]: "Hello, DerivedName"  

:zap: It’s important that a derived class follow its base class contract.

即,虽然派生类可以覆写基类的方法,但是应用遵守基类的契约。

class Base {
  greet() {
    console.log("Hello, base!");
  }
}

class Derived extends Base {
  // 错误,参数类型错误,Type '(name: string) => void' is not assignable to type '() => void'.
  greet(name:string){
     console.log("Hello, derived!");
  }
}

Initialization Order

The order of class initialization, as defined by JavaScript, is:

  • The base class fields are initialized
  • The base class constructor runs
  • The derived class fields are initialized
  • The derived class constructor runs

继承内置类

可以字定义类继承自Error、Array 、Map等内置类。

但是需要手动改下原型链。

成员可见性

public

类的成员默认就是public的,可以在类的外部访问

class Greeter {
  name = "jack"
  greet() {
    console.log("hi!");
  }
}
const g = new Greeter();
g.greet();
console.log(g.name)

protected

仅在类内部、以及派生类中可见。

class Greeter {
  protected sayHi() {
    return "hi";
  }
}

class SpecialGreeter extends Greeter {
  say() {
       // 子类可以访问sayHi
    console.log("Howdy, " + this.sayHi());
  }
}

const greater = new Greeter()
const g = new SpecialGreeter();
g.say()
// greater.sayHi() //Error, 内的外部不可见
// g.sayHi()  //Error, 内的外部不可见

:zap:注意:

前面,我们提到:派生类应该遵守基类的契约。

但是,可见性不受契约限制,派生类可以改变基类成员的可见性。

class Base {
  protected m = 10;
}
class Derived extends Base {
  // No modifier, so default is 'public'
  m = 15;
}
const d = new Derived();
console.log(d.m); // OK

private

仅在类内部可见。

class Base {
  private x = 0;
  getX(){
    return this.x
  }
}
const b = new Base();
console.log(b.getX())  //OK
// console.log(b.x); //Property 'x' is private and only accessible within class 'Base'.

:zap:注意:对于private修改的成员,子类不会继承

静态成员Static Members

静态成员包括静态属性和静态方法。

:zap: ES6中,Class 内部只有静态方法,没有静态属性(有个提案,还在stage3阶段)。

class MyClass {
  static x = 0;
  static printX() {
    console.log(MyClass.x);
  }
}
console.log(MyClass.x);
MyClass.printX();

// 编译为ES6为:
class MyClass {
    static printX() {
        console.log(MyClass.x);
    }
}
// ES6的静态属性
MyClass.x = 0;
console.log(MyClass.x);
MyClass.printX();

静态成员也可以同时有可见性修饰符,也可以被继承。

class MyClass {
  private static x = 0;
  static printX() {
    console.log(MyClass.x);
  }
}
// MyClass.x = 100   // 内外部无法访问私有的静态属性


//静态成员被继承
class Base {
  static getGreeting() {
    return "Hello world";
  }
}
class Derived extends Base {
  myGreeting = Derived.getGreeting();
}
const d = new Derived()
console.log(d.myGreeting)  // [LOG]: "Hello world" 

:zap: 注意,由于ES6中,静态属性实际上是挂载在类上的,而类本身就是可以用new调用的函数,所以静态成员的名称不能和函数原型上已有的属性同名,如:name/length/call

没有静态类

在Java中有静态内部类,但是Typescript不需要,因为普通函数和对象可以完成相同的工作。

// Unnecessary "static" class
class MyStaticClass {
  static doSomething() {}
}

// Preferred (alternative 1)
function doSomething() {}

// Preferred (alternative 2)
const MyHelperObject = {
  dosomething() {},
};

泛型类Generic Classes

ts中引入了泛型,这是ES6没有的。

泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。

class Box<Type> {
  contents: Type;
  constructor(value: Type) {
    this.contents = value;
  }
}

const b = new Box("hello!");
const c = new Box(666)

泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

class Box<Type> {
  static defaultValue: Type; //Error
 //Static members cannot reference class type parameters.
}

类中的this

:100: TypeScript 不会改变 JavaScript 的运行时行为。

由于javascript中this的处理非同寻常:

class MyClass {
  name = "MyClass";
  getName() {
    //this : 调用时的对象
    return this.name;
  }
}
const c = new MyClass();
const obj = {
  name: "obj",
  getName: c.getName,
};

// Prints "obj", not "MyClass"
// 因为getName是通过obj调用的,因为this指向obj,而不是指向MyClass的实例
console.log(obj.getName());


// 如果期望固定this的指向,建议使用箭头函数
class MyClass {
  name = "MyClass";
  getName = () => {
    return this.name;
  };
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());