- Description : There is currently no Chinese translation of the latest official documents of TypeScript on the Internet, so there is such a translation plan. Because I am also a beginner in TypeScript, I cannot guarantee that the translation will be 100% accurate. If there are errors, please point out in the comment section;
- translation content : The tentative translation content is TypeScript Handbook , and other parts of the translated document will be supplemented in the future;
- project address : TypeScript-Doc-Zh , if it helps you, you can click a star~
The official document address of this chapter: Classes
Background guide: (MDN)
kind
TypeScript provides comprehensive support for the class
Like other JavaScript language features, TypeScript also provides type annotations and other syntax for classes to help developers express the relationship between classes and other types.
Class member
This is the most basic class-it is empty:
class Point {}
This class is currently useless, so let's add some members to it.
Field
Declaring a field is equivalent to adding a public, writable attribute to the class:
class Point {
x: number;
y: number;
}
const pt = new Point()
pt.x = 0;
pt.y = 0;
Like other features, the type annotation here is also optional, but if the type is not specified, the type any
will be implicitly adopted.
Fields can also be initialized, and the initialization process will be performed automatically when the class is instantiated:
class Point {
x = 0;
y = 0;
}
const pt = new Point();
// 打印 0, 0
console.log(`${pt.x}, ${pt.y}`);
Just like using const
, let
and var
, the initialization statement of the class attribute will also be used for type inference:
const pt = new Point();
pt.x = "0";
// Type 'string' is not assignable to type 'number'.
--strictPropertyInitialization
The configuration item strictPropertyInitialization used to control whether the fields of the class need to be initialized in the constructor.
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";
}
}
Note that the fields need to be initialized inside the constructor itself. TypeScript does not analyze the methods called in the constructor to detect the initialization statement, because the derived class may override these methods, causing the initialization of members to fail.
If you insist on using methods other than the constructor (such as using an external library to fill in the contents of the class) to initialize a field, then you can use the deterministic assignment assertion operator !
:
class OKGreeter {
// 没有初始化,但不会报错
name!: string;
}
readonly
The field can be readonly
modifier to prevent the field from being assigned outside the constructor.
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
The constructor of a class is very similar to a function, you can add type annotations to its parameters, you can use parameter default values or function overloading:
class Point {
x: number;
y: number;
// 使用了参数默认值的正常签名
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
class Point {
// 使用重载
constructor(x: number, y: string);
constructor(s: string);
constructor(xs: any, y?: any) {
// TBD
}
}
There is only one difference between the constructor signature and the function signature of a class:
- The constructor cannot use type parameters-type parameters are part of the class declaration, we will learn about it later
- The constructor cannot add type annotations to the return value-the type it returns is always the type of the class instance
super
call
Like JavaScript, if you have a base class and a derived class, you super();
in the constructor before this.
in the derived class to access class members:
class Base {
k = 4;
}
class Derived extends Base {
constructor() {
// ES5 下打印出错误的值,ES6 下报错
console.log(this.k);
^
// 'super' must be called before accessing 'this' in the constructor of a derived class.
super();
}
}
In JavaScript, forgetting to call super
is a common mistake, but TypeScript will remind you when necessary.
method
The attribute of the class may be a function, and we call it a method at this time. Methods, like functions and constructors, can also use various type annotations:
class Point {
x = 10;
y = 10;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
Except for standard type annotations, TypeScript adds nothing new to methods.
Note that in the method body, you must pass this.
to access the fields and other methods of the class. Using non-compliant names in the method body will be regarded as accessing variables in the neighboring scope:
let x: number = 0;
class C {
x: string = "hello";
m() {
// 下面这句是在试图修改第一行的 x,而不是类的属性
x = "world";
^
// Type 'string' is not assignable to type 'number'.
}
}
Getters/Setters
Classes can also have accessors:
class C {
_length = 0;
get length(){
return this._length;
}
set length(value){
this._length = value;
}
}
Note: In JavaScript, a get/set pair without additional logic has no effect. If you do not need to add additional logic when performing get/set operations, you only need to expose the fields as public fields.
For accessors, TypeScript has some special inference rules:
- If
get
exists butset
does not exist, then the attribute will automatically become a read-only attribute - If the type of the setter parameter is not specified, the parameter type will be inferred based on the type of the getter return value
- The getter and setter must have the same member visibility .
Starting from TypeScript 4.3 , the getter and setter of the accessor can use different types.
class Thing {
_size = 0;
get size(): number {
return this._size;
}
set size(value: string | number | boolean) {
let num = Number(value);
// 不允许使用 NaN、Infinity 等
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
Index signature
Class can declare index signature, how it works and other object types of index signature the same:
class MyClass {
[s: string]: boolean | ((s: string) => boolean);
check(s: string) {
return this[s] as boolean;
}
}
Because index signature types also need to capture method types, it is not easy to use these types effectively. Generally, it is better to store index data in another location than the class instance itself.
Class inheritance
Like other object-oriented languages, classes in JavaScript can inherit from base classes.
implements
clause
You can use a implements
clause to check whether the class conforms to a specific interface. If the class does not implement this interface correctly, an error will be thrown:
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!");
}
}
The class can implement multiple interfaces, such as class C implements A,B {
.
Precautions
There is an important point to understand, that is, the implements
clause is only used to check whether the class can be regarded as a certain interface type, it will not change the type of the class or its methods at all. A common mistake is to think that the implements
clause will change the type of the class-in fact it won't!
interface Checkable {
check(name: string): boolean;
}
class NameChecker implements Checkable {
check(s) {
^
//Parameter 's' implicitly has an 'any' type.
// 注意这里不会抛出错误
return s.toLowercse() === "ok";
^
// any
}
}
In this example, we might think s
type of interface will be check
of name: string
impact parameter. But in fact it will not-the implements
clause will not have any effect on the inspection of the class content and type inference.
In the same way, implementing an interface with optional attributes will not create the attribute:
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
clause
A class can inherit from a base class. The derived class has all the attributes and methods of the base class, and can also define additional members.
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();
// 基类方法
d.move();
// 派生类方法
d.woof(3);
Rewrite method
Derived classes can also override the fields or attributes of the base class. You can use the super.
syntax to access the methods of the base class. Note that since the JavaScript class is just a simple search object, there is no concept of "parent class field".
TypeScript enforces that a derived class is always a subclass of the base class.
For example, the following is an example of a legal rewrite method:
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");
A very important point is that the derived class will follow the constraints of the base class. It is a common (and always legal!) practice to refer to a derived class through a base class reference:
// 通过一个基类引用去命名一个派生类实例
const b: Base = d;
// 没有问题
b.greet();
What if the derived class Derived
does not follow the constraints of the Base
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
// 让这个参数成为必选参数
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()}`);
}
}
If you ignore the error and compile the code, an error will be reported after the following code is executed:
const b: Base = new Derived();
// 因为 name 是 undefined,所以报错
b.greet();
Initialization sequence
The initialization order of JavaScript classes may surprise you in some cases. Let's take a look at the following code:
class Base {
name = "base";
constructor() {
console.log("My name is " + this.name);
}
}
class Derived extends Base {
name = "derived";
}
// 打印 base 而不是 derived
const d = new Derived();
What happened here?
According to the definition of JavaScript, the order of class initialization is:
- Initialize the fields of the base class
- Implement the constructor of the base class
- Initialize the fields of the derived class
- Implement the constructor of the derived class
This means that because the field of the derived class has not been initialized when the base class constructor is executed, the base class constructor can only see its own value name
Inheriting built-in types
Note: If you do not intend to inherit built-in types such as Array, Error, Map, or your compilation target is explicitly set to ES6/ES2015 or higher, then you can skip this part.
In ES2015, the constructor returns the object instance is implicitly this
value is replaced super(...)
any caller. Necessary that the generated code configured to capture super(...)
any potential return value, and with this
replace it.
Therefore, the Error
, Array
etc. may not be effective as expected. This is because constructors such as Error
and Array
use ES6's new.target
to adjust the prototype chain. However, when the constructor function is called in ES5, there is no similar method to ensure the value of new.target
By default, other low-level compilers usually have the same restrictions.
For a subclass like the following:
class MsgError extends Error {
constructor(m: string) {
super(m);
}
sayHello() {
return "hello " + this.message;
}
}
You may find:
- The method of the instance object returned after calling the subclass may be
undefined
, so callingsayHello
will throw an error instanceof
between the subclass instance and the subclass may be destroyed, so(new MsgError()) instanceof MsgError
will returnfalse
.
The recommended approach is to manually adjust the prototype chain super(...)
class MsgError extends Error {
constructor(m: string) {
super(m);
// 显式设置原型链
Object.setPrototypeOf(this, MsgError.prototype);
}
sayHello() {
return "hello " + this.message;
}
}
However, MsgError
also needs to set the prototype manually. that do not support 161b414400d4d4 Object.setPrototypeOf , you can use __proto__
.
Worse, these workarounds will not work on older versions of IE10 or the .aspx). You can manually copy method on the prototype to the instance (such as the MsgError.prototype
copy method to this
), but the prototype chain itself can not be repaired.
Member visibility
You can use TypeScript to control whether specific methods or properties are visible on the outside of the class.
public
The default visibility of class members is public ( public
). Public members can visit anywhere:
class Greeter {
public greet(){
console.log('hi!');
}
}
const g = new Greeter();
g.greet();
Since the visibility of members is public by default, you don't need to make explicit declarations in front of class members, but for code specifications or readability considerations, you can also do so.
protected
Protected ( protected
) members are only visible in subclasses of the class.
class Greeter {
public greet() {
console.log("Hello, " + this.getName());
}
protected getName() {
return "hi";
}
}
class SpecialGreeter extends Greeter {
public howdy() {
// 这里可以访问受保护成员
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.
Publicly protected members
Derived classes need to follow the constraints of their base classes, but can choose to expose subclasses of the base class with more functions. This includes making protected members into public members:
class Base {
protected m = 10;
}
class Derived extends Base {
// 没有修饰符,所以默认可见性是公有的
m = 15;
}
const d = new Dervied();
console.log(d.m); // OK
Note that Dervied
can read and write the member m
freely, so writing this way will not change the "security" of this situation. The main point to note here is that in the derived class, if we unintentionally disclose its members, then we need to add the protected
modifier.
Cross-level access to protected members
There are disputes between different OOP languages as to whether it is legal to access protected members through a base class reference:
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'.
}
}
For example, Java considers the above code to be legal, but C# and C++ consider the above code to be illegal.
TypeScript also think that this is illegal, because only in Derived2
access subclass Derived2
of x
is legitimate, but Derived1
not Derived2
subclasses. Moreover, if it is already illegal to access x
Derived1
reference (this should indeed be illegal!), then it should also be illegal to access it through the base class reference.
About why C# thinks this code is illegal, you can read this article for more information: Why can't I access a protected member in a derived class?
private
private
protected
, but it is declared that private
cannot be accessed even in the subclass:
class Base {
private x = 0;
}
const b = new Base();
// 无法在类外面访问
console.log(b.x);
// Property 'x' is private and only accessible within class 'Base'.
class Derived extends Base {
showX() {
// 无法在子类中访问
console.log(this.x);
^
// Property 'x' is private and only accessible within class 'Base'.
}
}
Since private members are not visible to derived classes, derived classes cannot improve their visibility:
class Base {
private x = 0;
}
class Dervied extends Base {
/*
Class 'Derived' incorrectly extends base class 'Base'.
Property 'x' is private in type 'Base' but not in type 'Derived'.
*/
x = 1;
}
Access private members across instances
There are disputes between different OOP languages as to whether it is legal for different instances of the same class to access each other's private members. Java, C#, C++, Swift, and PHP allow this, but Ruby considers it illegal.
TypeScript allows cross-instance access to private members:
class A {
private x = 10;
public sameAs(other: A) {
// 不会报错
return other.x === this.x;
}
}
Precautions
Like everything else in the TypeScript type system, private
and protected
only take effect during type checking, .
This means that some operations during the JavaScript runtime, such as in
or simple attribute lookup, can still access private or protected members:
class MySafe {
private serectKey = 123345;
}
// 在 JavaScript 文件中会打印 12345
const s = new MySafe();
console.log(s.secretKey);
And even during type checking, we can access private members through square bracket syntax. Therefore, during operations such as unit testing, it is easier to access private fields, but the disadvantage is that these fields are "weakly private" and cannot guarantee privacy in a strict sense.
class MySafe {
private secretKey = 12345;
}
const s = new MySafe();
// 在类型检查期间,不允许这样访问私有成员
console.log(s.secretKey);
^
// Property 'secretKey' is private and only accessible within class 'MySafe'.
// 但是可以通过方括号语法访问
console.log(s["secretKey"]);
And TypeScript with private
different private members statement, JavaScript with #
declared private field After compiling and still is private and does not provide like that above the square brackets syntax is used to access private members, so JavaScript is private members "Strongly Private".
class Dog {
#barkAmount = 0;
personality = 'happy';
constructor() {}
}
Take the following TypeScript code as an example:
"use strict";
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() { }
}
After compiling it into ES2021 or lower version code, TypeScript will use WeakMap instead of #
.
"use strict";
var _Dog_barkAmount;
class Dog {
constructor() {
_Dog_barkAmount.set(this, 0);
this.personality = "happy";
}
}
_Dog_barkAmount = new WeakMap();
If you need to protect the values in the class from malicious modification, then you should use mechanisms that provide runtime privacy protection, such as closures, WeakMaps, or private fields. Note that these privacy checks added at runtime may affect performance.
Static member
Background guide: static member (MDN)
Classes can have static ( static
) members. These members have nothing to do with the specific instance of the class, we can access them through the class constructor object itself:
class MyClass {
static x = 0;
static printX(){
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
Static members can also use visibility modifiers such as public
, protected
and private
class MyClass {
private static x = 0;
}
console.log(MyClass.x);
^
// Property 'x' is private and only accessible within class 'MyClass'.
Static members can also be inherited:
class Base {
static getGreeting() {
return "Hello world";
}
}
class Derived extends Base {
myGreeting = Derived.getGreeting();
}
Special static member name
It is generally unsafe/impossible to Function
Because the class itself is also a new
, some specific static member names cannot be used. Function attributes such as name
, length
and call
cannot be used as static member names:
class S {
static name = 'S!';
^
// Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'.
}
Why is there no static class?
TypeScript (and JavaScript) does not provide the structure of static classes like C# and Java.
C# and Java need static classes because these languages require all data and functions to be placed in one class. Because this restriction does not exist in TypeScirpt, there is no need for static classes. A class with only a single instance is usually represented by an ordinary object in JavaScript/TypeScirpt.
For example, in TypeScript we don't need "static class" syntax, because a regular object (or even a top-level function) can also accomplish the same job:
// 不必要的静态类
class MyStaticClass {
static doSomething() {}
}
// 首选(方案一)
function doSomething() {}
// 首选(方案二)
const MyHelperObject = {
dosomething() {},
};
Static block in class
Static blocks allow you to write a series of declaration statements that have their own scope and can access private fields in the containing class. This means that we can write initialization code, which contains declaration statements, will not have the problem of variable leakage, and can fully access the internals of the class.
class Foo {
static #count = 0;
get count(){
return Foo.#count;
}
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}
Generic class
Like interfaces, classes can also use generics. When new
, its type parameters are inferred just like in the function call:
class Box<Type> {
contents: Type;
constructor(value: Type){
this.contents = value;
}
}
const b = new Box('hello!');
^
// const b: Box<string>
Classes can use generic constraints and default values just like interfaces.
Type parameters in static members
The following code is illegal, but the reason may not be so obvious:
class Box<Type> {
static defaultValue: Type;
^
// Static members cannot reference class type parameters.
}
Remember, types are always completely erased after compilation! At runtime, there is only one attribute slot Box.defaultValue
This means that setting Box<string>.defaultValue
(if it can be set) will also change Box<number>.defaultValue
-this will not work. Static members of a generic class can never refer to the type parameters of the class.
Class runtime this
One important point to remember is that TypeScript does not change the runtime behavior of JavaScript. As we all know, JavaScript has some special runtime behaviors.
JavaScript this
is indeed very unusual:
class MyClass {
name = "MyClass";
getName() {
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
// 打印 "obj" 而不是 "MyClass"
console.log(obj.getName());
Long story short, by default, the function this
value depends how the function is being called . In this case, since we are by obj
to call function references, so it's this
value is obj
, not an instance.
This is usually not the result we expect! TypeScript provides some methods so that we can reduce or prevent the occurrence of such errors.
Arrow function
If your function often loses the this
context when it is called, it is better to use arrow function attributes instead of method definitions:
class MyClass {
name = 'MyClass';
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// 打印 MyClass
console.log(g());
There are some trade-offs to this approach:
- It can be guaranteed that
this
is correct at runtime, even for those codes that are not checked using TypeScript - This will take up more memory, because functions defined in this way will cause each class instance to have a copy of the function
- You cannot use
super.getName
in a derived class because there is no entry on the prototype chain to get the method of the base class
this
parameters
In a TypeScript method or function definition, if the name of the first parameter is this
, then it has a special meaning. Such parameters will be erased during compilation:
// TypeScript 接受 this 参数
function fn(this: SomeType, x: number) {
/* ... */
}
// 输出得 JavaScript
function fn(x) {
/* ... */
}
TypeScript checks whether the function call passed in the this
parameter is in the correct context. Here we did not use the arrow function, but added a this
parameter to the method definition to ensure that the method can be called correctly in a static way:
class MyClass {
name = "MyClass";
getName(this: MyClass) {
return this.name;
}
}
const c = new MyClass();
// OK
c.getName();
// 报错
const g = c.getName;
console.log(g());
// The 'this' context of type 'void' is not assignable to method's 'this' of type 'MyClass'.
The trade-off between the pros and cons of this method is the opposite of the above method of using arrow functions:
- The caller of JavaScript may still call class methods by mistake without realizing it
- Only assign a function to each class definition, instead of assigning a function to each class instance
- The method defined by the base class can still be
super
this
type
In the class, this
can dynamically reference the type of the current class. Let's take a look at how it works:
class Box {
contents: string = "";
set(value: string){
^
// (method) Box.set(value: string): this
this.contents = value;
return this;
}
}
Here, TypeScript set
the return value type of this
to 061b414400de08 instead of Box
. Now let's create a Box
:
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}
const a = new ClearableBox();
const b = a.set("hello");
^
// const b: ClearableBox
this
in the type annotation of the parameter:
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}
This other: Box
-if you have a derived class, then its sameAs
method will only accept other instances of that derived class:
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'.
*/
Type protection based on this
this is Type
annotation of the return value type of the method of the class and interface. When this statement is used with type contraction (for example, the if
statement), the target object type will be contracted to the specified 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
}
A common use case for type protection based on this
is to allow delayed verification of specific fields. In the following code as an example, when hasValue
be verified as true, it can be removed Box
for undefined
the value
values:
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 attributes
TypeScript provides a special syntax to convert constructor parameters into class attributes with the same name and value. This grammar is called a parameter attribute, and it is implemented by adding one of the visibility modifiers such as public
, private
, protected
or readonly
The final field will get these modifiers:
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// 没有必要编写构造器的函数体
}
}
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 expression
Background guide: type expression (MDN)
Class expressions and class declarations are very similar. The only difference is that class expressions do not need names, but we can still refer to them through any identifier bound to the class expression:
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
In TypeScript, classes, methods, and fields may be abstract.
There is no corresponding implementation of abstract methods or abstract fields in the class. These members must exist in an abstract class that cannot be instantiated directly.
The role of an abstract class is to act as a base class, allowing its subclasses to implement all abstract members. When a class does not have any abstract members, we say that it is concrete.
Let's look at an example:
abstract class Base {
abstract getName(): string;
printName(){
console.log("Hello, " + this.getName());
}
}
const b = new Base();
// Cannot create an instance of an abstract class.
Because Base
is an abstract class, we cannot use new
to instantiate it. Instead, we need to create a derived class and let it implement abstract members:
class Derived extends Base {
getName() {
rteurn "world";
}
}
const d = new Derived();
d.printName();
Note that if we forget to implement the abstract member of the base class, an error will be thrown:
class Derived extends Base {
^
// Non-abstract class 'Derived' does not implement inherited abstract member 'getName' from class 'Base'.
// 忘记实现抽象成员
}
Abstract construction signature
Sometimes you want to accept a class constructor function as a parameter, let it produce an instance of a certain class, and this class is derived from an abstract class.
For example, you might want to write code like this:
function greet(ctor: typeof Base) {
const instance = new ctor();
// Cannot create an instance of an abstract class.
instance.printName();
}
TypeScript will correctly tell you that you are trying to instantiate an abstract class. After all, according to greet
, it should be completely legal to write such code, and it will eventually construct an instance of an abstract class:
// 不行!
greet(Base);
But it will actually report an error. Therefore, the parameters accepted by the function you write should have a construction signature:
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.
*/
Now TypeScript can correctly tell you which class constructor function can be called- Derived
can be called because it is a concrete class, but Base
cannot be called because it is an abstract class.
Connection between classes
In most cases, classes in TypeScript are compared structurally, just like other types.
For example, the following two classes can replace each other because they are exactly the same in structure:
class Point1 {
x = 0;
y = 0;
}
class Point2 {
x = 0;
y = 0;
}
// OK
const p: Point1 = new Point2();
Similarly, even if the inheritance relationship is not explicitly declared, there can be a subclass relationship between a class and a class:
class Person {
name: string;
age: number;
}
class Employee {
name: string;
age: number;
salary: number;
}
// OK
const p: Person = new Employee();
This sounds simple and easy to understand, but there are some situations that can be strange.
The empty class has no members. In a structured type system, a type without members is usually a superclass of any other type. So if you write an empty class (don't do this!), then you can replace it with any type:
class Empty {}
function fn(x: Empty) {
// 无法对 x 执行任何操作,所以不建议这么写
}
// 这些参数都是可以传入的!
fn(window);
fn({});
fn(fn);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。