TypeScript 的官网文档早已更新,但我能找到的中文文档都还停留在比拟老的版本。所以对其中新增以及订正较多的一些章节进行了翻译整顿。

本篇翻译整顿自 TypeScript Handbook 中 「Classes」 章节。

本文并不严格依照原文翻译,对局部内容也做了解释补充。

动态成员(Static Members)

类能够有动态成员,动态成员跟类实例没有关系,能够通过类自身拜访到:

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

动态成员同样能够应用 public protectedprivate 这些可见性修饰符:

class MyClass {  private static x = 0;}console.log(MyClass.x);// Property 'x' is private and only accessible within class 'MyClass'.

动态成员也能够被继承:

class Base {  static getGreeting() {    return "Hello world";  }}class Derived extends Base {  myGreeting = Derived.getGreeting();}

非凡动态名称(Special Static Names)

类自身是函数,而覆写 Function 原型上的属性通常认为是不平安的,因而不能应用一些固定的动态名称,函数属性像 namelengthcall 不能被用来定义 static 成员:

class S {  static name = "S!";  // Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.}

为什么没有动态类?(Why No Static Classes?)

TypeScript(和 JavaScript) 并没有名为动态类(static class)的构造,然而像 C# 和 Java 有。

所谓动态类,指的是作为类的动态成员存在于某个类的外部的类。比方这种:

// javapublic class OuterClass {  private static String a = "1";    static class InnerClass {      private int b = 2;  }}

动态类之所以存在是因为这些语言强制所有的数据和函数都要在一个类外部,但这个限度在 TypeScript 中并不存在,所以也没有动态类的须要。一个只有一个独自实例的类,在 JavaScript/TypeScript 中,齐全能够应用一般的对象代替。

举个例子,咱们不须要一个 static class 语法,因为 TypeScript 中一个惯例对象(或者顶级函数)能够实现一样的性能:

// Unnecessary "static" classclass MyStaticClass {  static doSomething() {}} // Preferred (alternative 1)function doSomething() {} // Preferred (alternative 2)const MyHelperObject = {  dosomething() {},};

类动态块(static Blocks in Classes)

动态块容许你写一系列有本人作用域的语句,也能够获取类里的公有字段。这意味着咱们能够安心的写初始化代码:失常书写语句,无变量透露,还能够齐全获取类中的属性和办法。

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

泛型类(Generic Classes)

类跟接口一样,也能够写泛型。当应用 new 实例化一个泛型类,它的类型参数的推断跟函数调用是同样的形式:

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

类跟接口一样也能够应用泛型束缚以及默认值。

动态成员中的类型参数(Type Parameters in Static Members)

这代码并不非法,然而起因可能并没有那么显著:

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

记住类型会被齐全抹除,运行时,只有一个 Box.defaultValue 属性槽。这也意味着如果设置 Box<string>.defaultValue 是能够的话,这也会扭转 Box<number>.defaultValue,而这样是不好的。

所以泛型类的动态成员不应该援用类的类型参数。

类运行时的 this(this at Runtime in Classes)

TypeScript 并不会更改 JavaScript 运行时的行为,并且 JavaScript 有时会呈现一些奇怪的运行时行为。

就比方 JavaScript 解决 this 就很奇怪:

class MyClass {  name = "MyClass";  getName() {    return this.name;  }}const c = new MyClass();const obj = {  name: "obj",  getName: c.getName,}; // Prints "obj", not "MyClass"console.log(obj.getName());

默认状况下,函数中 this 的值取决于函数是如何被调用的。在这个例子中,因为函数通过 obj 被调用,所以 this 的值是 obj 而不是类实例。

这显然不是你所心愿的。TypeScript 提供了一些形式缓解或者阻止这种谬误。

箭头函数(Arrow Functions)

如果你有一个函数,常常在被调用的时候失落 this 上下文,应用一个箭头函数或者更好些。

class MyClass {  name = "MyClass";  getName = () => {    return this.name;  };}const c = new MyClass();const g = c.getName;// Prints "MyClass" instead of crashingconsole.log(g());

这里有几点须要留神下:

  • this 的值在运行时是正确的,即便 TypeScript 不查看代码
  • 这会应用更多的内存,因为每一个类实例都会拷贝一遍这个函数。
  • 你不能在派生类应用 super.getName ,因为在原型链中并没有入口能够获取基类办法。

this 参数(this parameters)

在 TypeScript 办法或者函数的定义中,第一个参数且名字为 this 有非凡的含意。该参数会在编译的时候被抹除:

// TypeScript input with 'this' parameterfunction fn(this: SomeType, x: number) {  /* ... */}
// JavaScript outputfunction fn(x) {  /* ... */}

TypeScript 会查看一个有 this 参数的函数在调用时是否有一个正确的上下文。不像上个例子应用箭头函数,咱们能够给办法定义增加一个 this 参数,动态强制办法被正确调用:

class MyClass {  name = "MyClass";  getName(this: MyClass) {    return this.name;  }}const c = new MyClass();// OKc.getName(); // Error, would crashconst g = c.getName;console.log(g());// The 'this' context of type 'void' is not assignable to method's 'this' of type 'MyClass'.

这个办法也有一些留神点,正好跟箭头函数相同:

  • JavaScript 调用者仍然可能在没有意识到它的时候谬误应用类办法
  • 每个类一个函数,而不是每一个类实例一个函数
  • 基类办法定义仍然能够通过 super 调用

this 类型(this Types)

在类中,有一个非凡的名为 this 的类型,会动静的援用以后类的类型,让咱们看下它的用法:

class Box {  contents: string = "";  set(value: string) {    // (method) Box.set(value: string): this    this.contents = value;    return this;  }}

这里,TypeScript 推断 set 的返回类型为 this 而不是 Box 。让咱们写一个 Box 的子类:

class ClearableBox extends Box {  clear() {    this.contents = "";  }} const a = new ClearableBox();const b = a.set("hello");// const b: ClearableBox

你也能够在参数类型注解中应用 this

class Box {  content: string = "";  sameAs(other: this) {    return other.content === this.content;  }}

不同于写 other: Box ,如果你有一个派生类,它的 sameAs 办法只承受来自同一个派生类的实例。

class Box {  content: string = "";  sameAs(other: this) {    return other.content === this.content;  }} class DerivedBox extends Box {  otherContent: string = "?";} const base = new Box();const derived = new DerivedBox();derived.sameAs(base);// Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'.  // Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.

基于 this 的类型爱护(this-based type guards)

你能够在类和接口的办法返回的地位,应用 this is Type 。当搭配应用类型收窄 (举个例子,if 语句),指标对象的类型会被收窄为更具体的 Type

class FileSystemObject {  isFile(): this is FileRep {    return this instanceof FileRep;  }  isDirectory(): this is Directory {    return this instanceof Directory;  }  isNetworked(): this is Networked & this {    return this.networked;  }  constructor(public path: string, private networked: boolean) {}} class FileRep extends FileSystemObject {  constructor(path: string, public content: string) {    super(path, false);  }} class Directory extends FileSystemObject {  children: FileSystemObject[];} interface Networked {  host: string;} const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo"); if (fso.isFile()) {  fso.content;  // const fso: FileRep} else if (fso.isDirectory()) {  fso.children;  // const fso: Directory} else if (fso.isNetworked()) {  fso.host;  // const fso: Networked & FileSystemObject}

一个常见的基于 this 的类型爱护的应用例子,会对一个特定的字段进行懒校验(lazy validation)。举个例子,在这个例子中,当 hasValue 被验证为 true 时,会从类型中移除 undefined

class Box<T> {  value?: T;   hasValue(): this is { value: T } {    return this.value !== undefined;  }} const box = new Box();box.value = "Gameboy"; box.value;  // (property) Box<unknown>.value?: unknown if (box.hasValue()) {  box.value;  // (property) value: unknown}

参数属性(Parameter Properties)

TypeScript 提供了非凡的语法,能够把一个结构函数参数转成一个同名同值的类属性。这些就被称为参数属性(parameter properties)。你能够通过在结构函数参数前增加一个可见性修饰符 public private protected 或者 readonly 来创立参数属性,最初这些类属性字段也会失去这些修饰符:

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);// (property) Params.x: numberconsole.log(a.z);// Property 'z' is private and only accessible within class 'Params'.

类表达式(Class Expressions)

类表达式跟类申明十分相似,惟一不同的是类表达式不须要一个名字,只管咱们能够通过绑定的标识符进行援用:

const someClass = class<Type> {  content: Type;  constructor(value: Type) {    this.content = value;  }}; const m = new someClass("Hello, world");  // const m: someClass<string>

抽象类和成员(abstract Classes and Members)

TypeScript 中,类、办法、字段都能够是形象的(abstract)。

形象办法或者形象字段是不提供实现的。这些成员必须存在在一个抽象类中,这个抽象类也不能间接被实例化。

抽象类的作用是作为子类的基类,让子类实现所有的形象成员。当一个类没有任何形象成员,他就会被认为是具体的(concrete)。

让咱们看个例子:

abstract class Base {  abstract getName(): string;   printName() {    console.log("Hello, " + this.getName());  }} const b = new Base();// Cannot create an instance of an abstract class.

咱们不能应用 new 实例 Base 因为它是抽象类。咱们须要写一个派生类,并且实现形象成员。

class Derived extends Base {  getName() {    return "world";  }} const d = new Derived();d.printName();

留神,如果咱们遗记实现基类的形象成员,咱们会失去一个报错:

class Derived extends Base {    // Non-abstract class 'Derived' does not implement inherited abstract member 'getName' from class 'Base'.  // forgot to do anything}

形象结构签名(Abstract Construct Signatures)

有的时候,你心愿承受传入能够继承一些抽象类产生一个类的实例的类构造函数。

举个例子,你兴许会写这样的代码:

function greet(ctor: typeof Base) {  const instance = new ctor();    // Cannot create an instance of an abstract class.  instance.printName();}

TypeScript 会报错,通知你正在尝试实例化一个抽象类。毕竟,依据 greet 的定义,这段代码应该是非法的:

// Bad!greet(Base);

但如果你写一个函数承受传入一个结构签名:

function greet(ctor: new () => Base) {  const instance = new ctor();  instance.printName();}greet(Derived);greet(Base);// Argument of type 'typeof Base' is not assignable to parameter of type 'new () => Base'.// Cannot assign an abstract constructor type to a non-abstract constructor type.

当初 TypeScript 会正确的通知你,哪一个类构造函数能够被调用,Derived 能够,因为它是具体的,而 Base 是不能的。

类之间的关系(Relationships Between Classes)

大部分时候,TypeScript 的类跟其余类型一样,会被结构性比拟。

举个例子,这两个类能够用于代替彼此,因为它们构造是相等的:

class Point1 {  x = 0;  y = 0;} class Point2 {  x = 0;  y = 0;} // OKconst p: Point1 = new Point2();

相似的还有,类的子类型之间能够建设关系,即便没有显著的继承:

class Person {  name: string;  age: number;} class Employee {  name: string;  age: number;  salary: number;} // OKconst p: Person = new Employee();

这听起来有些简略,但还有一些例子能够看出奇怪的中央。

空类没有任何成员。在一个结构化类型零碎中,没有成员的类型通常是任何其余类型的父类型。所以如果你写一个空类(只是举例,你可不要这样做),任何货色都能够用来替换它:

class Empty {} function fn(x: Empty) {  // can't do anything with 'x', so I won't} // All OK!fn(window);fn({});fn(fn);

TypeScript 系列

  1. TypeScript 之 根底入门
  2. TypeScript 之 常见类型(上)
  3. TypeScript 之 常见类型(下)
  4. TypeScript 之 类型收窄
  5. TypeScript 之 函数
  6. TypeScript 之 对象类型
  7. TypeScript 之 泛型
  8. TypeScript 之 Keyof 操作符
  9. TypeScript 之 Typeof 操作符
  10. TypeScript 之 索引拜访类型
  11. TypeScript 之 条件类型
  12. TypeScript 之 映射类型
  13. TypeScript 之 模板字面量类型
  14. TypeScript 之 类(上)

微信:「mqyqingfeng」,加我进冴羽惟一的读者群。

如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。