二.进阶
1.类型推断:
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
2.联合类型:
联合类型(Union Types)表示取值可以为多种类型中的一种,使用" | "分隔每个类型。联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型。
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
myFavoriteNumber = true;//index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
3.类型别名:
类型别名用来给一个类型起个新名字。使用 type 可以创建类型别名,类型别名常用于联合类型。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。
4.字符串字面量类型:
字符串字面量类型用来约束取值只能是某几个字符串中的一个。我们使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。注意,类型别名与字符串字面量类型都是使用 type 进行定义。
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'
5.接口:
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
let tom: Person = {
name: 'Tom'
};
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
可见,赋值的时候,变量的形状必须和接口的形状保持一致。
⑴可选属性:
该属性可选,使用?标志,但仍然不允许添加未定义的属性。
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};//报错,仍然不允许添加未定义的属性
⑵任意属性:
使用 [propName: string] 定义了任意属性取 string 类型的值。一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集。
interface Person {
name: string;
age?: number;
[propName: string]: string;
//改为[propName: string]: any;即正确
//也可以改为联合类型[propName: string]: string | number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
//任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了
⑶只读属性:
可以用 readonly
定义只读属性。
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
6.类:
⑴public private 和 protected:
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public
、private
和 protected
。
public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
的。private
修饰的属性或方法是私有的,不能在声明它的类的外部访问。protected
修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的。
注:
①使用 private
修饰的属性或方法是无法直接存取的,在子类中也是不允许访问的;而如果是用 protected
修饰,则允许在子类中访问。需要注意的是,TypeScript 编译之后的代码中,并没有限制 private
属性在外部的可访问性。
class Animal {
public name;
private type;
public constructor(name, type) {
this.name = name;
this.type = type;
}
}
let a = new Animal('Jack', 'human');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
a.type = 'monkey';
console.log(a.type); // index.ts(9,13): error TS2341: Property 'type' is private and only accessible within class 'Animal'.
②当构造函数 constructor
修饰为 private
时,该类不允许被继承或者实例化;当构造函数 constructor
修饰为 protected
时,该类只允许被继承。
⑵参数属性和readonly:
修饰符和 readonly
还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。只读属性关键字 readonly
,只允许出现在属性声明或索引签名或构造函数中。注意如果 readonly
和其他访问修饰符同时存在的话,需要写在其后面。
class Animal {
// public name: string;
public constructor(public name) {
//public constructor(public readonly name) {
// this.name = name;
}
}
class Animal {
readonly name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.
⑶抽象类:
abstract
用于定义抽象类和其中的抽象方法。首先,抽象类是不允许被实例化的,其次,抽象类中的抽象方法必须被子类实现。即使是抽象方法,TypeScript
的编译结果中,仍然会存在这个类。
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() {//需要实现抽象方法
console.log(`Meow, My name is ${this.name}`);
}
}
let a = new Animal('Jack');//不能实例化抽象类
//index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
let cat = new Cat('Tom');//只能实例化子类
⑷类的类型:
给类加上 TypeScript 的类型很简单,与接口类似:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
7.类与接口:
一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces
),用 implements
关键字来实现。
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Door {
}
//一个接口可以被多个类继承
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
//一个类可以继承多个接口
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
//接口和接口也可以继承
interface LightableAlarm extends Alarm {
lightOn(): void;
lightOff(): void;
}
注:接口也可以继承类,但是不是标准的类继承。
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
//等价于:
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface PointInstanceType {
x: number;
y: number;
}
// 等价于 interface Point3d extends PointInstanceType
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
当我们声明 interface Point3d extends Point 时,Point3d 继承的实际上是类 Point 的实例的类型。
换句话说,可以理解为定义了一个接口 Point3d 继承另一个接口 PointInstanceType。值得注意的是,PointInstanceType 相比于 Point,缺少了 constructor 方法,声明 Point 类时创建的 Point 类型只包含其中的实例属性和实例方法。
8.泛型:
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。简单来说,就是用来指定函数、接口或类输入/输出时的类型。
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
//当然,也可以不手动指定,而让类型推论自动推算出来
createArray(3, 'x'); // ['x', 'x', 'x']
上例中,我们在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。
⑴多个类型参数:
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
⑵泛型约束:
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length
属性的变量。这就是泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
多个类型参数之间也可以互相约束:
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
//其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。
⑶泛型接口:
可以使用接口的方式来定义一个函数需要符合的形状。
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
⑷泛型类:
与泛型接口类似,泛型也可以用于类的类型定义中。我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
//泛型类的默认类型
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。