参考文章:
类
一个简单的类
TypeScript 中类的写法与 C# 或 Java 等面向对象语言的差不多,一个简单的类示例如下:
// 类
class Person {
// 属性
name: string;
// 构造函数
constructor(name: string) {
this.name = name;
}
// 方法
sayHello(): void {
console.log(`Hello, ${this.name}! `);
}
}
// 实例对象
const person = new Person('WJT20');
person.sayHello(); // "Hello, WJT20! "
继承
继承的写法也和其他面向对象语言一样,以下是一个继承实例,我们创建一个 Postman 类继承 Person 类:
class Person {
...
}
class Postman extends Person {
constructor(name: string) {
super(name); // 将name传给父类
}
// 重写sayHello()方法,但不影响父类
sayHello(): void {
console.log(`Hello, I am postman——${this.name}! `);
}
sendMail(): void {
console.log(`${ this.name }: This is your mail`); // this.name来自父类
}
}
const postman = new Postman('WJT20');
postman.sayHello(); // 调用重写方法
postman.sendMail(); // 调用自身方法
派生类(子类)包含了一个构造函数,它必须调用
super()
,它会执行基类(父类)的构造函数。 而且,在构造函数里访问 this 的属性之前,我们一定要调用super()
。这个是 TypeScript 强制执行的一条重要规则。
修饰符
- 公有修饰符(public)
默认情况下,类中的所有成员都是使用public
修饰符,也就是说 Person 类中属性和方法也可以这样写:
class Person {
public name: string;
public constructor(name: string) {
this.name = name;
}
public sayHello(): void {
console.log(`Hello, ${this.name}! `);
}
}
- 私有修饰符(private)
使用private
关键字修饰的成员,只能在该类的内部访问:
class Person {
public name: string;
private info: string = 'none';
public constructor(name: string, info?: string) {
this.name = name;
this.info = info || 'none';
this.getMyInfo(); // 可调用
}
private getMyInfo(): void {
console.log(`${this.name}'s info => ${this.info}`);
}
}
const person = new Person('WJT20', 'A programmer. ');
person.getMyInfo(); // 不可调用
console.log(person.name); // 可访问
console.log(person.info); // 不可访问
- 受保护修饰符(protected)
protected
修饰符的行为与private
修饰符类似,不同之处在于protected
修饰的成员可以在子类中访问,而private
则不能:
class Person {
public name: string;
private privateInfo: string = 'none';
protected protectedInfo: string = 'none';
public constructor(name: string, info?: string) {
this.name = name;
this.privateInfo = info || 'none';
this.protectedInfo = info || 'none';
}
}
class Postman extends Person {
constructor(name: string, info?: string) {
super(name, info);
console.log(this.privateInfo); // 不可访问
console.log(this.protectedInfo); // 可访问
}
}
const postman = new Postman('WJT20', 'A postman. ');
- 只读修饰符(readonly)
readonly
修饰符可以将属性设置为只读类型,只读属性只能在声明或在构造函数内初始化:
class Person {
public name: string;
readonly type: string;
public constructor(name: string) {
this.name = name;
this.type = 'human'; // 赋值成功
}
}
const person = new Person('WJT20');
person.type = 'alien'; // 赋值失败
- 参数属性
通过给构造函数的参数添加修饰符,可以实现参数属性,仔细观察前面我写的代码,初始化属性我是这样做的:
class Person {
private name: string;
public constructor(name: string) {
// 根据构造函数传入的参数初始化属性
this.name = name;
}
}
使用参数属性改造后:
class Person {
public constructor(private name: string) {}
}
看上去是不是简洁了很多。
存取器机制
对于public
修饰的类成员来说,无论是在类的内部还是外部,我们都可以控制这些成员的取值,例如:
class Person {
public constructor(public name: string) {}
}
const person = new Person('WJT20');
console.log(person.name); // "WJT20"
person.name = 'WJT21';
console.log(person.name); // "WJT21"
某些时候,我们会用private
修饰原本公有的成员,以保证数据的安全,只提供指定的存取器 api(getter/setter)给外部:
class Person {
public constructor(private _name: string) {}
// getter
get name(): string {
return this._name;
}
// setter
// 注意,setter不能指定类型
set name(name: string) {
this._name = name;
}
}
const person = new Person('WJT20');
console.log(person.name); // "WJT20"
person.name = 'WJT21';
console.log(person.name); // "WJT21"
静态属性
类的静态成员,指的是那些存在于类本身上面而不是类的实例上的类成员(前面我们接触到的都是存在于类实例上的),要定义静态成员,可以使用static
关键字:
class Person {
static type: string = 'human'; // 静态属性type
public constructor(private _name: string) {}
...
}
console.log(Person.type); // "human"
抽象类
抽象类做为其它派生类的基类使用。它们一般不会直接被实例化。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。使用
abstract
关键字可以定义抽象类和在抽象类内部定义抽象方法。
abstract class Person {
static type: string = 'human';
abstract sayHello(): void; // 在派生类中实现,注意静态成员的写法
constructor(public name: string) {}
}
class Postman extends Person {
constructor(name: string) {
super(name);
}
sayHello(): void {
console.log(`Hello, I am ${this.name}`);
}
}
const person = new Person('WJT20'); // 实例化失败
const postman = new Postman('WJT20'); // 实例化成功
postman.sayHello();
泛型
泛型函数
形如:
function identity<T>(arg: T): T {
return arg;
}
函数identity()
叫作泛型函数,其中 T 被称为类型变量(名称自定义,以下均命名为 T),它是一种特殊的变量,只用于表示类型而不是值。T 帮助我们捕获用户传入的类型,并允许在上下文中通过引用 T 来复用传入的类型。
调用identity()
有两种方式:
// 直接声明T的类型
const res1 = identity<string>("Api");
// 极简模式,由编译器自行判断T的类型
const res2 = identity("Api");
需要注意的,如果入参传的是一个数组,T 表示的就是元素类型,而不是入参类型,所以出入参的类型表示应该是”T[]”而不是”T”:
/** 编译错误 */
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
/** 编译正确 */
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
泛型接口
有时候我们需要定义泛型的接口,以上文的identity()
为例,可以有以下两种写法,使用方法略有不同:
1)
interface Identity1 {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
const myIdentity: Identity1 = identity;
2)
interface Identity2<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
const myIdentity: Identity2<number> = identity;
泛型类
泛型类的写法与泛型接口的写法相似,定义一个泛型类,可以这么写:
class KeyString<T> {
getNumber: (a: T, b: T) => T;
}
KeyString 类一旦声明了 T 的类型,那么getNumber()
函数的传参类型就必须与 T 一致,否则会编译报错:
/** 编译错误 */
const key = new KeyString<string>();
key.getNumber = (a: number, b: number) => (a + b)
console.log(key.getNumber(1, 2)); // error!
/** 编译正确 */
const key = new KeyString<number>();
key.getNumber = (a: number, b: number) => (a + b)
console.log(key.getNumber(1, 2)); // 3
/** 编译正确 */
const key = new KeyString<string>();
key.getNumber = (a: string, b: string) => (a + b)
console.log(key.getNumber('1', '2')); // "12"
泛型约束
泛型约束,顾名思义就是给泛型增加约束条件,那么如何增加约束条件呢?这里以上文的泛型函数loggingIdentity()
为例,我们现在要给loggingIdentity()
增加约束,出入参都必须要有 length 这个属性,可以这么做:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
下面是泛型约束中使用多个类型参数,更高级的用法示例:
interface A<T> {
p: T;
}
function say<T, U extends A<T>>(a: T, b: U) {
console.log(a, b);
}
say(1, { p: 2 }); // 1 {p: 2}
say('1', { p: 2 }); // error!
say(1, { p: '2' }); // error!
约束<T, U extends A<T>>
表示say()
的第二个参数(A类型)的 p 属性和第一个参数类型一致,否则报错。