TypeScript
提供了对class
非常丰富的支持。
与其它JavaScript
语言功能一样,TypeScript
添加了类型注释
和其它的语法来让你来表达class
和其它的类型
之间的联系。
类型成员(Class Members)
写一个最基础的class
—— 一个空的class
:
class Point {}
这个class
还不是非常有用,让我们添加一些成员
。
字段(Fields)
字段声明
在类上创建公共可写属性:
class Point {
x: number;
y: number;
}
const pt = new Point();
pt.x = 0;
pt.y = 0;
这里对字段
的类型注释
是可选的,但如果未指定,那么将会是any
类型。
字段
也可以被初始化:
class Point {
x = 0;
y = 0;
}
const pt = new Point();
// Prints 0, 0
console.log(`${pt.x}, ${pt.y}`);
就像const
,let
和var
,类的初始化值
会决定属性的类型
:
const pt = new Point();
pt.x = "0";
// Type 'string' is not assignable to type 'number'.
--strictPropertyInitialization
--strictPropertyInitialization
可以控制class
的字段是否一定需要被构造函数
初始化:
class BadGreeter {
name: string;
// Property 'name' has no initializer and is not definitely assigned in the constructor.
}
class GoodGreeter {
name: string;
constructor() {
this.name = "hello";
}
}
我们注意到字段必须在构造函数内初始化
。TypeScript
不会分析从构造函数
调用的方法来检测是否初始化
,因为派生类
可能会覆盖这些方法并且无法初始化成员。
如果你确定会在以外的构造函数
中初始化,那么你也可以使用断言
操作符"!":
class OKGreeter {
// Not initialized, but no error
name!: string;
}
readonly
字段
或许有readonly
前缀。这保护字段
在构造函数
外不会被赋值。
class Greeter {
readonly name: string = "world";
constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}
err() {
this.name = "not ok";
// Cannot assign to 'name' because it is a read-only property.
}
}
const g = new Greeter();
g.name = "also not ok";
// Cannot assign to 'name' because it is a read-only property.
构造函数(Constructor)
类的构造函数
和函数
非常类似。你可以添加有类型注释
的参数,默认值
,或者重载
:
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
}
}
类的构造函数
和函数签名
略微有些不同:
构造函数
不能用泛型构造函数
不能有返回类型声明。
调用super函数(Super Calls)
就像在JavaScript
中一样,如果你有一个基类·
,那你必须调用super()
;并且在使用this
之前调用:
class Base {
k = 4;
}
class Derived extends Base {
constructor() {
// Prints a wrong value in ES5; throws exception in ES6
console.log(this.k);
// 'super' must be called before accessing 'this' in the constructor of a derived class.
super();
}
}
在JavaScript
中,忘记调用super
是非常容易出错的,但是TypeScript
会告诉你什么时候需要调用super
。
方法(Methods)
在class
中,函数属性
被称之为方法
。方法
可以使用和函数
和构造函数
相同的类型注释
:
class Point {
x = 10;
y = 10;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
除了标准的类型注释
,TypeScript
不需要为方法
添加任何其它东西。
请注意在方法
内部,必须通过this
访问字段。一个不合格
的名称:
let x: number = 0;
class C {
x: string = "hello";
m() {
// This is trying to modify 'x' from line 1, not the class property
x = "world";
// Type 'string' is not assignable to type 'number'.
}
}
在函数体中用没有使用this
修饰的变量
,将始终引用外层作用域
的变量
。
Getters/Setters
class
也可以有访问器
。
class C {
_length = 0;
get length() {
return this._length;
}
set length(value) {
this._length = value;
}
}
TypeScript
对于访问器
有一些特殊的推断规则:
- 如果
get
存在,但是没有set
,那么这个属性会被自动推到为readonly
. - 如果
setter
的参数没有指定类型,那么属性的类型会从getter
的返回类型中推导出来。 Getters
和setters
必须要有相同的访问级别
。
从TypeScript 4.3
开始,允许getting
和setting
有不同的类型:
class Thing {
_size = 0;
get size(): number {
return this._size;
}
set size(value: string | number | boolean) {
let num = Number(value);
// Don't allow NaN, Infinity, etc
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
索引签名(Index Signatures)
class
可以定义索引签名
:
class MyClass {
[s: string]: boolean | ((s: string) => boolean);
check(s: string) {
return this[s] as boolean;
}
}
Class的其它特性(Class Heritage)
和其他的面向对象的语言特点一样,在JavaScript
中,class
可以在从其它class
中继承
。
实现规则
(implements Clauses)
你可以使用implements
来检查类
是否满足特定接口。如果class
没有正确的实现
一个接口的话,那么将会提示一个错误:
interface Pingable {
ping(): void;
}
class Sonar implements Pingable {
ping() {
console.log("ping!");
}
}
class Ball implements Pingable {
// Class 'Ball' incorrectly implements interface 'Pingable'.
// Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
pong() {
console.log("pong!");
}
}
Classes
或许也可以实现
多个接口,class C implements A, B {
。
注意事项(Cautions)
重要的是要理解implements
字句只是检查类是否可以把视为interface
类型。这不会在改变类
的类型或其方法。一个常见的错误是,认为interface
的实现会改变类型,实际上是不会的。
interface Checkable {
check(name: string): boolean;
}
class NameChecker implements Checkable {
check(s) {
// Parameter 's' implicitly has an 'any' type.
// Notice no error here
return s.toLowercse() === "ok";
any
}
}
在这个例子当中,我们也许希望参数s
的类型被自动推导为check
的参数name
的string
类型。但实际上并没有——implements
的字句不会改变检查类
或推断其类型
。
类似的,实现一个有可选属性的接口也不会创建任何类型:
interface A {
x: number;
y?: number;
}
class C implements A {
x = 0;
}
const c = new C();
c.y = 10;
Property 'y' does not exist on type 'C'.
extends
的细节(extends Clauses)
Class
或许会从一个基础class
中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);
重写方法(Overriding Methods)
衍生类
也可以覆盖
基础类的字段
或者属性
。你可以使用super
语法,来访问基类
的方法。请注意,因为JavaScript
类是一个简单的查找对象,所以没有"super field"的概念。
TypeScript
强制派生类
始终是其基类
的子类型
。
举个例子,这是重写方法的合法方式:
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
greet(name?: string) {
if (name === undefined) {
super.greet();
} else {
console.log(`Hello, ${name.toUpperCase()}`);
}
}
}
const d = new Derived();
d.greet();
d.greet("reader");
派生类
基于基类
是非常重要的一种约束。请记住,通过基类
引用来引用
派生类实例时很常见的:
// Alias the derived instance through a base class reference
const b: Base = d;
// No problem
b.greet();
如果Derived
没有遵守Base
呢?:
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
// Make this parameter required
greet(name: string) {
// Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'.
// Type '(name: string) => void' is not assignable to type '() => void'.
console.log(`Hello, ${name.toUpperCase()}`);
}
}
如果我们不顾错误编译这段代码,那么可能会发生崩溃性的错误:
const b: Base = new Derived();
// Crashes because "name" will be undefined
b.greet();
仅类型字段声明(Type-only Field Declarations)
当target >= ES2022
或者useDefineForClassFields
是true
的时候,class
的字段在父类构造函数完成后
初始化,覆盖
父类设置的任何值。当你想要去为继承字段
重新声明更准确的类型
时,这会是一个问题。为了处理这些问题,你可以写declare
来向TypeScript
表明此字段没有运行时影响。
interface Animal {
dateOfBirth: any;
}
interface Dog extends Animal {
breed: any;
}
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
class DogHouse extends AnimalHouse {
// Does not emit JavaScript code,
// only ensures the types are correct
declare resident: Dog;
constructor(dog: Dog) {
super(dog);
}
}
初始化顺序(Initialization Order)
在某些情况下,JavaScript
类的初始化顺序可能会让人意外:
class Base {
name = "base";
constructor() {
console.log("My name is " + this.name);
}
}
class Derived extends Base {
name = "derived";
}
// Prints "base", not "derived"
const d = new Derived();
这里发生了什么?
类的初始化顺序,用JavaScript
定义就是:
基类的字段
初始化- 积累的
构造函数
运行 - 子类的字段初始化
- 子类的构造函数运行
这代表子类
的构造函数
看见的时自己的name
值,因为父类的字段
至今没有初始化。
继承内置类型(Inheriting Built-in Types)
在ES2015
中,返回对象的构造函数
隐式地将this
的值替换为super(...)
的任何调用者。这是非常用用的对于泛型构造函数
去捕获任何super(...)
潜在的返回值并且替换this·
。
因此,Error
,Array
,和其它类型或许不再按预期工作。这是由于构造函数
对于Error
,Array
和像使用ECMAScript 6
的new.target
来调整原型链
有关。但是,在ECMAScript5
中调用构造函数时,无法确保new.target
的值。
一个子类可能看起来像是这样的:
class MsgError extends Error {
constructor(m: string) {
super(m);
}
sayHello() {
return "hello " + this.message;
}
}
你或许会发现:
- 在对象上,在初始化这些子类的时候,方法或许是
undefined
,所以调用sayHello
可能会发生错误。 instanceof
将在子类的实例以及其实例之间中断,所以(new MsgError()) instanceof MsgError
会返回false
。
作为一个建议,你可以在任何super(...)
调用后立即手动调整原型
。
class MsgError extends Error {
constructor(m: string) {
super(m);
// Set the prototype explicitly.
Object.setPrototypeOf(this, MsgError.prototype);
}
sayHello() {
return "hello " + this.message;
}
}
但是,任何MsgError
的子类也必须手动设置原型
。对于不支持运行时的Object.setPrototypeOf
,你可以用__proto__
代替。
成员的访问性(Member Visibility)
你可以使用TypeScript
来控制方法
和属性
对于外部的可见性。
public
默认的成员可见性是public
。一个public
的成员可以在任何地方被访问:
class Greeter {
public greet() {
console.log("hi!");
}
}
const g = new Greeter();
g.greet();
因为public
已经是默认的可见性修饰符,你不再需要显示的添加它。
protected
protected
的成员对本类
以及子类
可见。
class Greeter {
public greet() {
console.log("Hello, " + this.getName());
}
protected getName() {
return "hi";
}
}
class SpecialGreeter extends Greeter {
public howdy() {
// OK to access protected member here
console.log("Howdy, " + this.getName());
}
}
const g = new SpecialGreeter();
g.greet(); // OK
g.getName();
// Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.
受保护成员的暴露(Exposure of protected memebers)
子类
需要遵守父类的一些规则
,但可以选择公开具有更多功能的基类的子类型。例如将受保护的成员公开:
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
请注意,Derived
以及能够自由读写m,所以这不会有意义地改变这种情况的“安全性”。这里主要要注意的是,在
派生类中,如果这种暴露不是故意的,我们需要小心重复受保护的
修饰符`。
跨层次保护访问(Cross-hierarchy protected access)
不同的OOP
语言对于通过基类
引用访问受保护成员是否合法存在分歧。
class Base {
protected x: number = 1;
}
class Derived1 extends Base {
protected x: number = 5;
}
class Derived2 extends Base {
f1(other: Derived2) {
other.x = 10;
}
f2(other: Base) {
other.x = 10;
// Property 'x' is protected and only accessible through an instance of class 'Derived2'. This is an instance of class 'Base'.
}
}
例如,Java
认为这是合法的,但是C#
和C++
选择此代码应该是非法的。
private
private
和protected
类似,但是不允许子类访问成员:
class Base {
private x = 0;
}
const b = new Base();
// Can't access from outside the class
console.log(b.x);
Property 'x' is private and only accessible within class 'Base'.
class Derived extends Base {
showX() {
// Can't access in subclasses
console.log(this.x);
Property 'x' is private and only accessible within class 'Base'.
}
}
Try
Because
因为private
的成员在子类
中不能被访问,所以子类不能增加它的可见度
:
class A {
private x = 10;
public sameAs(other: A) {
// No error
return other.x === this.x;
}
}
注意事项(Caveats)
像TypeScript
类型系统的其他方面,private
和protected
只是在编码期间检查。
这就代表在JavaScript
的运行时中,类似in
或者属性访问依然可以访问private
或者protected
的成员:
class MySafe {
private secretKey = 12345;
}
// In a JavaScript file...
const s = new MySafe();
// Will print 12345
console.log(s.secretKey);
private
还允许在类型检查期间使用括号表示发惊醒访问。这使得私有字段可能更容易访问单元测试等内容,缺点是这些字段是
class MySafe {
private secretKey = 12345;
}
const s = new MySafe();
// Not allowed during type checking
console.log(s.secretKey);
// 'secretKey' is private and only accessible within class 'MySafe'.
// OK
console.log(s["secretKey"]);
不像TypeScript
的private
,JavaScript
的private fields(#)
在编译后仍然是私有的,并且不提供前面提到的舱口,如括号符号访问 。
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() {}
}
"use strict";
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() { }
}
当编译到ES2021
或者更近
的版本,TypeScript
将使用WeakMaps
代替#
。
"use strict";
var _Dog_barkAmount;
class Dog {
constructor() {
_Dog_barkAmount.set(this, 0);
this.personality = "happy";
}
}
_Dog_barkAmount = new WeakMap();
静态成员(Static Members)
class
可能有静态
的成员。这些成员不与类的特定实例相关联。它们可以通过类
访问:
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
静态成员也可以使用public
,protected
,和private
来修饰:
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)
从函数原型覆盖属性通常是不安全的。因为class
本身可以被new
调用,但是static
的名字不能被new
调用。函数
的属性name
,length
,和call
不能被再定义:
class S {
static name = "S!";
// Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.
}
为什么没有静态类(Why No Static Classes)
TypeScript(and JavaScript)
没有静态类一样的构造,但是C#
这种有。
这些结构之所以存在,是因为这些语言强制所有数据
和函数
都在类中。因为TypeScript
中不存在该限制,所以不需要它们。只有一个实例的类通常只标识为JavaScript/TypeScript
中的普通对象。
// Unnecessary "static" class
class MyStaticClass {
static doSomething() {}
}
// Preferred (alternative 1)
function doSomething() {}
// Preferred (alternative 2)
const MyHelperObject = {
dosomething() {},
};
举个例子,我们不需要TypeScript
中的"静态类"语法,因为常规对象(甚至顶级函数)也可以完成这项工作:
// Unnecessary "static" class
class MyStaticClass {
static doSomething() {}
}
// Preferred (alternative 1)
function doSomething() {}
// Preferred (alternative 2)
const MyHelperObject = {
dosomething() {},
};
静态块(Block in Classes)
静态块
允许您编写具有自己范围的语句序列
,这些语
句可以访问包含类中的私有字
段。这意味着我们可以编写具有静态语句的所有功能的初始化代码,不会泄漏变量,并且可以完全访问我们类的内部。
class Foo {
static #count = 0;
get count() {
return Foo.#count;
}
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}
泛型类(Generic Classes)
class
和interfaces
很像,也可以使用泛型
。当使用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
----not good。泛型类的静态成员永远不能引用类的类型参数。
this在运行时(this at Runtime in Classes)
记住TypeScript
不会改变JavaScript
的运行时行为说非常重要的,并且JavaScript
以具有一些特殊的运行时行为而闻名。
JavaScript
对此的处理确实不同寻常:
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)
如果你有一个经常丢失上下文的函数,使用箭头函数
而不是方法定义会更有意义:
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());
这有一些权衡:
this
的值保证在运行时是正确的,即使对于未使用TypeScript
检查的代码也是如此。- 这将多使用更多内存,因为每个类实例都会有自己的
- 您不能在子类中使用super.getName,因为原型链中没有条目可以从中获取积累方法。
this 参数(this parameters)
在一个方法或者函数定义中,在TypeScript
中,this
的参数有特殊的意义。这些参数在编译期间被擦除:
// TypeScript input with 'this' parameter
function fn(this: SomeType, x: number) {
/* ... */
}
// JavaScript output
function fn(x) {
/* ... */
}
TypeScript
检查是否使用正确的上下文调用带有this
参数的函数。我们可以在方法顶一个this
参数来静态地强制方法被正确的调用:
class MyClass {
name = "MyClass";
getName(this: MyClass) {
return this.name;
}
}
const c = new MyClass();
// OK
c.getName();
// Error, would crash
const 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)
在class
中,成为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语句)混合时,目标对象的类型将被缩小到指定类型。
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
的类型保护的一个常见用例时允许对特定字段进行延迟验证。举个例子,当hasValue
被验证为true
时,这种情况会从box
内保存的值中删除一个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
提一个一个特殊的语法,用于将构造函数参数转换为具有相同名称和值的类属性。
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: number
console.log(a.z);
//Property 'z' is private and only accessible within class 'Params'.
Class表达式(Class Expressions)
类表达式与类声明非常相似。真正的区别只有表达式不需要name
,尽管我们可以通过它们最终绑定到的任何标识符来引用它们:
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 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
正确地告诉你可以调用哪些类构造函数。
类之间的联系(Releationships Between Classes)
大多数情况下,TypeScript
中的类在结构上进行比较,与其它类型相同。
例如,这两个类可以互相替代使用,因为它们是相同的:
class Point1 {
x = 0;
y = 0;
}
class Point2 {
x = 0;
y = 0;
}
// OK
const p: Point1 = new Point2();
同样,即使没有显示继承,类之间也存在子类型
关系:
class Person {
name: string;
age: number;
}
class Employee {
name: string;
age: number;
salary: number;
}
// OK
const 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);