注意,数据类型大小写不同。如Boolean
是构造函数而boolean
是基本类型,如Object
包含数组,而object
不包含。
类型兼容
在TS符合鸭子类型,只要两个类型内部结构相同即兼容
类型注解
声明变量时若没有设置类型和初始值,则类型为any
,初始值为undefined
。
声明变量若指定了初始值但没有指定类型,则触发类型推论,自动附加类型。
基本类型
- boolean
- number
- string
-
undefined 和 null
两者各自有自己的类型分别叫做undefined
和null
。
未开启--strictNullChecks
时,null
和undefined
是所有类型的子类型,例如可以把null
赋值给number
类型的变量。 - void
方法无返回,或return
无值,或return undefined
object
所有非基本类型的父集
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create(undefined); // Error
数组
- 类型+括号表示法
let a: number[] = [1, 2, 3];
let c: (number | string)[] = [1, 2, "3"];
let d: number[][] = [[1,2],[3,4]];
- 数组泛型
Array<>
let b: Array<number | string> = [1, 2, "3"];
- 用接口表示数组
通常仅用于类数组对象,如以下IArguments
是内置接口
// interface IArguments {
// [index: number]: any;
// length: number;
// callee: Function;
// }
function sum() {
let args: IArguments = arguments;
}
元组(元素数量和类型固定的数组)
当访问一个越界的元素,会使用联合类型替代
let x: [string, number];
x = ['hello', 10];
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
any
声明变量时若没有设置类型和初始值,则类型为any
,初始值为undefined
。
any类型会跳过编译阶段的类型检查,可以任意赋值或调用属性,不建议使用。
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
unknown
安全版的any
,任何类型都能分配给unknown
,而unknown
只能分配给unknown
、any
或者未声明类型
。
unknow
类型必须类型断言转化为其他类型后才能操作,弥补了any
的缺点。
let c: unknown = 100;
c+=10;//Object is of type 'unknown'
(c as number) += 10;
(<number>c) += 10;
let d = c as number;
never
方法不会执行完(抛出异常或无限循环)
never
可以被赋值给任何类型,但任何类型都不能赋值给never
// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
}
let x: never;
// 运行错误,数字类型不能转为 never 类型
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();
联合类型
满足其中一种类型即可赋值
但后续只能访问此联合类型中所有类型共有的属性或方法:
function fn(x: boolean | string):void{
console.log(x.length);//error boolean上不存在属性length
}
字面量类型
直接将字面量作为一个类型,通常配合联合类型使用可起到类似枚举的作用
let num: 1 | 2 | 3 = 1;
let str: "a" | "b" | "c" = c;
类型推论
如声明变量时,指定了初始值但没有指定类型,则触发类型推论,自动附加类型。
如既没有初始值又没有指定类型,则推断为any
。
let num = 'seven';
num = "7";
num = 7;//error TS2322: Type 'number' is not assignable to type 'string'.
let num2;
num2 = 'seven';
num2 = 7;
类型断言(建议只变为更精细,不要变为更模糊)
可通过值 as 类型
或<类型>值
进行断言,不建议使用后者,因容易与泛型混淆。
类型断言只会影响 TypeScript 编译时的类型,类型断言语句在编译结果中会被删除。
通常用于为any
、unknow
、联合类型、外部传入内容、父类对象等,指定更精确的数据类型。当断言为更模糊的数据类型时要慎用,因等同于绕过了编译器的类型检查,可能会造成运行时错误。
let a:number = 666;
let aLength1 = a.length;//报错 类型number不存在属性length
let aLength2 = (a as any).length;//不报错
通过双重断言甚至可以将一个类型先断言为any,再断言为任一其他类型。
let a:string = "hello";
(a as any as number) = 100;
类型声明比类型断言更严谨,因类型不会变更模糊:
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
const animal: Animal = {
name: 'tom'
};
不报错 鸭子类型,虽然没有使用 interface Cat extends Animal,依然视为兼容
let fake_tom = animal as Cat;
报错
let real_tom: Cat = animal;
类型别名 type
使用type
关键字定义数据类型,方便对数据类型进行管理和复用
//字面量类型
let num_1:1|2|3 = 1;
type Num_123 = 1 | 2 | 3;
let num_2: Num_123 = 1;
//元组
let tuple_1: [string, number] = ["1", 1];
type Tuple = [string, number];
let tuple_2: Tuple = ["1", 1];
interface IContact {
name: string; // 姓名
phone?: number; // 手机号
email: string; // 邮箱
avatar: string; // 头像
}
type Contact = IContact
- Omit 用于去除类型中某些字段
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type OmitContact = Omit<Contact, 'email' | 'avatar'>;
const obj_omit: OmitContact = {
name: "vv"
};
- Pick 用于只保留类型中某些字段
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type PickContact = Pick<Contact, 'name' | 'phone'>;
const obj_pick: PickContact = {
name: "vv"
};
- Partial 用于把类型中所有字段变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialContact = Partial<Contact>;
const obj_partial: PartialContact = {};
- Required 用于把类型中所有字段变为必选
type Required<T> = {
[P in keyof T]-?: T[P];
};
type RequiredContact = Required<Contact>;
const obj_required: RequiredContact = {
name: "vv",
phone: 111,
email: "123@qq.com",
avatar: "XXX"
};
- 也可以自行创建一个类型,如添加字段:
type WithId<T, P = number> = T & { id: P; };
type ContactWithId = WithId<PickContact>;
const obj_with_id: ContactWithId = {
name: "vv",
id: 1
};
枚举 enum
- 数字枚举
默认从0依次递增,也可以自己设置
可以用key取value,也可以用value取key
enum Color {Red = 1, Green, Blue};
var c: Color = Color.Green;//2
var c2: string= Color[2];//"Green"
//事实上此时对应的js为:
//Color[Color["Green"] = 2] = "Green";
//即Color.Green为2,且Color[2]为'Green'
- 字符串枚举
只能用key取value
enum Color {Red = "red", Sat = <any>"S"};
var c:Color = Color["Red"];
接口 interface
接口是对行为的抽象,而具体如何行动需要由类去实现(implement)。此外,接口也可用于对象形状的描述。在TS中,其类型检查符合“鸭式辨型法”也称做“结构性子类型化”。
因接口是类型而非真正的值,在编译结果中会被删除,无法通过instanceof
等判断。
当一个接口为空对象{}
时,其相关的类型校验总是通过
当重复声明同名接口时,会合并声明为一个接口(前提是两次声明的内容无冲突)
- 可选属性
接口中定义的属性必须被实现,若可不实现,则应使用可选属性:key?: value;
- 只读属性
仅能在对象创建时赋值,之后不能修改:readonly key:value;
- 任意属性(可索引类型)
接口中未定义的属性不可被实现,若要实现,则应使用可索引类型
一个接口中只能有一个任意属性,且其他属性的类型必须是任意属性的子集(若存在可选属性,则任意属性的属性应包含undefined
或any
)
interface Person {
name: string;
age?: number;
[propName: string]: string | number | undefined | number[];
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male',
params: [1,2,3]
};
对象接口
interface ILabel {
label: string;
name?: string;//可选参数
readonly age: number;//只读参数,仅能在对象刚创建时赋值,之后不能修改
todo?():void;//方法属性
sayHello?:() => string;//方法属性
}
function printLabel(labelledObj: ILabel ) {
...
}
printLabel( {size: 10, label: "Size 10 Object",age: 18} );//会报错
函数接口
函数参数名不需要与接口里定义的名字相匹配
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
if (result == -1) {
return false;
}else {
return true;
}
}
console.log(mySearch('hello','h'));
类接口
类使用implements
实现接口,详见“类”一章
混合接口
因JS灵活的特点,一个变量可能同时可以作为函数被调用,又具有对象的属性,此时其接口为一个混合接口
interface Counter {
(source: string, subString: string): boolean;
label: string;
}
接口继承(扩展)
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {//一个接口可以继承多个
sideLength: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;//必须同时满足两个接口
TS中声明一个类时,其实同时为该类声明了一个同名接口。因此和其他面向对象语言不同,TS的接口可以继承类,其本质上即继承该类的接口
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};
案例:用接口表示类数组
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
类
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。
强化了静态属性
采用了ES7提案,废除了原型成员。
static
不仅可修饰方法,也能修饰属性。类内直接定义的无static
属性,均为实例属性而非原型属性。
实例属性必须先在类中声明,然后再通过this
赋值
可为构造函数中的形参添加修饰符(访问修饰符 或 readonly)来简写该过程,自动为类声明该实例属性并赋值。
class Box {
name: string; # js中可以省略该行,但TS中会报错
constructor(name: string) {
this.name = name;
}
}
# 或简写为以下形式
class Box {
constructor(public name: string) {}
}
修饰符
只读修饰符 readonly
被设置为只读的属性,只能在声明时或构造函数里被初始化,之后无法修改。-
访问修饰符
- public
默认值,公有 - private
私有,不能在声明它的类的外部访问(包括实例)
构造函数为私有时,该类不能被继承和实例化
两个类即使有同名private
成员也无法兼容,除非该成员有同一来源 - protected
受保护,类似private
,但在子类中也能访问
构造函数为受保护时,该类只能被继承,不能被实例化
- public
继承(extends)
不同于python,JS、TS、C#都只能继承单个类
包含constructor
函数的派生类必须调用super()
,它会执行基类的构造方法。
class Animal{
name:string;
age:number;
constructor(theName:string='my name'){
this.name=theName;
}
move(distance:number=100){
console.log(this.name + 'move' + distance)
}
show(){
console.log('this is show')
}
}
let cat=new Animal('puppy');
cat.move(666)
class Dog extends Animal{
name:string;//子类只能让基类的属性更具体,而不能违反;不声明时即和基类相同
weight:number;
constructor(ddd){
super(ddd);//constructor内super必须在this之前
this.weight=20;
}
move(){//子类方法可以覆盖基类
console.log(this.name+this.weight);
super.show()
}
}
let dog=new Dog('ddd');
dog.move()
实现接口(implements)
和C#一样,TS中的类可以同时实现多个接口。
- 接口仅描述类的部分成员
类可自行声明更多成员,不违反接口。 - 接口仅描述类的公有成员
接口内描述的成员都是public。 - 接口仅描述类的实例成员
如静态成员、constructor等,不在描述范围内。
interface IBox {
name: string;
}
interface IAnimal {
age: number;
}
class BoxAnimal implements IBox, IAnimal {
static currentTime(): Date { return new Date() };
name: string = "TK";
age: number;
sex: number = 0;
constructor(age: number) {
this.age = age;
}
}
let tk: BoxAnimal = new BoxAnimal(18);
console.log(tk);
抽象类与抽象方法 abstract
- 带有
abstract
关键字的类称为抽象类,是供其它类继承的基类,介于类和接口之间,可以包含或不包含成员的实现细节,但不能被实例化。 - 带有
abstract
关键字的方法称为抽象方法,只能在抽象类中定义。抽象方法不包含具体实现并且必须在派生类中实现。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
把类当做接口使用
在TS中,接口(interface)和类(class)的界限并不清晰,因此可以用一个类实现另一个类,或者一个接口继承一个类。
同理,当重复声明同名类时,会合并声明为一个类(前提是两次声明的内容无冲突)
此外,TS也符合鸭子类型。注意在鸭子类型中,a:A
并不代表a是A或其子类的实例,只是形状相符而已。
interface IBox {
name: string;
}
interface IAnimal {
age: number;
}
class BoxAnimal implements IBox, IAnimal {
static currentTime(): Date { return new Date() };
name: string = "TK";
age: number;
sex: number = 0;
constructor(age: number) {
this.age = age;
}
}
let tk: BoxAnimal = new BoxAnimal(18);
type TBoxAnimal = BoxAnimal;
interface IBoxAnimal extends BoxAnimal { };
let tk_2: TBoxAnimal = { name: "11", age: 18, sex: 0 };
let tk_3: IBoxAnimal = { name: "11", age: 18, sex: 0 };
对象
Typescript 中的对象不能随意增删属性字段:
let my_custom_date = 20;
(window as any).my_custom_date = 30;
var obj = {
name: 'ming',
};
// 事实上触发了如下类型推论:
// var obj: { name: string; } = {
// name: 'ming',
// };
// obj.age = 19; //报错
obj["age"] = 19; //通过字典形式可以绕开类型检查
(obj as { name: string, age?: number; }).age = 19;//通过类型断言为obj添加可用属性
函数
函数声明方式定义函数:
function sum(x: number, y: number): number {
return x + y;
}
函数表达式方式定义函数:
let myAdd: (baseValue:number, increment:number) => number =
function(x: number, y: number): number { return x + y; };
注意在 TypeScript 的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
参数
传递给一个函数的参数个数必须与函数期望的参数个数一致
- 可选参数
必须写在必选参数之后 :
function fn(a:number,b?:any){ }
fn(100)
- 参数默认值
带默认值的参数也可以不传。
不传或传入undefined
时,会采用默认值
function fn(a:number,b='hello'){ }
fn(100)
- 剩余参数
function(a,b,...c: any[]){ }
- 函数重载
ts的函数重载比较特殊,重载和具体实现是分开书写的。
根据传入参数的不同,依次查找并尝试重载列表。 因此定义重载时,要把最精确的定义放在最前面。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
注意,这里只有两个重载:一个是接收对象另一个接收数字。
function reverse(x: number | string): number | string | void
并不是重载列表的一部分,需包含重载列表里所有的出入参情况,也可以写成function reverse(x: any): any
。
泛型(Generics)
指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
泛型函数
保证返回值的类型与传入参数的类型相同。
通过重载也可以实现该功能,但使用泛型更方便。
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
泛型接口
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
也可以把泛型参数提前到接口名上,则使用泛型接口时,需要定义泛型的类型。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
泛型类
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; };
泛型约束
泛型因未知其实际类型,无法在函数内调用属性或方法。通过继承接口可告知编译器泛型的形状,称之为泛型约束
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;
}
泛型之间也可以互相约束
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 });
泛型参数默认类型
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
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;
}
内置对象
详见TypeScript核心库定义文件
注意,NodeJS不是内置对象,如需使用,则应引入@types文件:
npm install @types/node --save-dev
- ECMA 内置对象
Boolean、Error、Date、RegExp 等。 - DOM 和 BOM 的内置对象
Document、HTMLElement、Event、NodeList 等。
可以将变量定义为这些类型:
let b: Boolean = new Boolean(1);