- 基础类型
- 接口
- 函数
- 字面量类型
- 联合类型和交叉类型(Unions and Intersection Types)
- 类
- 泛型(Generics)
- 枚举
- 变量声明
- 类型推论
- 类型兼容性
- 高级类型
- Symbols
- 迭代器和生成器
- 模块
- 命名空间
参考资料: https://www.tslang.cn/docs/handbook/ https://www.typescriptlang.org/docs/handbook/
基础类型
ts中支持的基础数据类型
- boolean:布尔值
- number:数字
- string:字符串
- [] / number[] / Array<number>:数组
- [string, number]:元组(表示一个已知元素数量和类型的数组,各元素类型不必相同)
- enum:枚举(为一组数值赋予友好的名字)
- any:不进行类型检查的任意类型
- void:没有任何类型(通常表示无返回值)
- null
- undefined
- never:永远不存在值的类型(总会抛出异常/无返回值的函数/箭头函数返回值/函数存在无法到达的终点)
- object
类型断言
- 表示人为确定某值应该当作什么类型去处理
-
有两种写法:
let val = "a string"; // 尖括号语法 let valLen1: number = (<string>val).length; // as 语法 let valLen2: number = (val as string).length;
接口
- 接口作用:
- 为类型命名
- 为代码定义契约
interface LabelledValue {
label: string;
}
function printLabel(labelObj: LabelledValue) {
console.log(labelObj.label);
}
// it‘s ok
printLabel({
label: "hello",
});
// error TS2345(对象字面量会经过额外属性检查,若存在目标类型不包含的属性时就会报错)
printLabel({
label: "hello",
name: "world"
});
let obj = {
label: "hello",
name: "world"
}
// 变量引用可绕过额外属性检查
printLabel(obj);
// 使用类型断言可以绕过额外属性检查
printLabel({ label: "hello", name: "world" } as LabelledValue);
// 使用字符串索引签名在接口中定义一个属性,表示该类型对象会带有任意数量的其他属性
interface Student {
name: string;
[propName: string]: any;
}
对象类型接口
-
可选属性:接口中定义可选的属性字段(对可能存在对属性进行预定义,引用不存在属性时将抛出错误)
interface Person { name: string; age?: number; }
-
只读属性:接口中定义只能在对象刚刚创建时修改其值的属性
interface Person { readonly name: string; }
函数类型接口
- 接口也可描述函数类型(包括参数类型和返回值类型)
interface QueryPerson {
(name: string, age: number): boolean;
}
let ifPersonExist: QueryPerson;
// 参数名可以不一样,参数和返回值类型可以不写 ts会依据接口进行类型推断
ifPersonExist = function(name: string, age: number): boolean {
return true;
}
可索引类型接口
- 可索引类型具有一个索引签名,它描述对象索引(key)的类型,还有相应的索引返回值(value)的类型
- ts支持两种索引类型:number,string
- 同时使用两种索引类型时,二者的value类型需保持相同(因为都是转为string类型索引的)
- 可以在接口中设置readonly,防止给索引赋值
interface StringArr {
// 表示key为number类型,value为string类型,并且不可修改
readonly [index: number]: string;
}
let myArr: StringArr;
myArr = ["bob", "fred"];
let firstStr: string = myArr[0];
类类型接口
- 明确的强制一个类去符合某种契约
- 接口描述了类的公共部分,而不是公共和私有两部分。它不会帮你检查类是否具有某些私有成员。
interface Person {
name: string;
sayHi(name: string);
}
class Student implements Person {
name: string;
sayHi(name: string) {
console.log('hello', name);
}
constructor(name: string) { }
}
混合类型接口
- 一个对象同时具备多种类型时需要的契约
- 场景:接入js第三方库
// 假设一个对象同时可作为函数和对象使用,并带有额外的属性
interface Person {
// 本身可当作函数执行
(play: string): void;
// 可获取的额外属性
name: string;
// 可获取的额外方法属性
sleep(time: number);
}
function child(): Person {
let person = <Person>function (play: string) {
console.log("play", play);
}
person.name = "Jim";
person.sleep = (time: number) {
console.log("sleep", time);
}
return person;
}
let xiaoMin = child();
xiaoMing("foot ball");
xiaoMing.name = "xiaoMing";
xiaoming.sleep(20);
接口继承接口
- 接口继承让我们可以灵活的分割接口,达到代码复用的目的。
interface Person {
name: string;
}
interface Action {
walk: boolean;
}
interface Student extends Person, Action {
number: string;
}
let xiaoMing = <Student>{};
xiaoMing.name = "xiaoMing";
xiaoMing.number = "2016222";
xiaoMing.walk = true;
接口继承类
- 继承类的成员但不包括其实现(只声明,不提供具体实现)
- 该接口类型只能被这个类或其子类所实现(implements)
class Control {
private state: any;
}
// 接口继承类
interface SelectableControl extends Control {
select(): void;
}
// 该接口类型只能被这个类或者子类实现
class Button extends Control implements SelectbleControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// error! Image类不是Control类或其子类,缺少state属性
class Image implements SelectableControl {
select() { }
}
class Location {
}
函数
函数类型
函数类型包含了参数类型和返回值类型
let func(x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
function func(x: number, y: number): number {
return x + y;
}
可选参数和默认参数
- 可选参数在参数名旁用“?”表示可选
- 可选参数必须跟在必须参数后面
- 默认参数表示传入参数为undefined时使用默认值
- 使用扩展运算符收集剩余的多个参数
重载
- 如果想定义的函数参数和返回值可能出现多种情况,为函数提供多个函数类型定义来进行函数重载
- 编译器会从函数定义列表第一个开始尝试定义,所以第一个一定要是最精确的定义
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: { suit: string; card: number; }[]):number;
function pickCard(x: number): { suit: string; card: number; };
function pickCard(x): any {
if (typeof x === 'object') {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
else if (typeof x === 'number') {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
字面量类型
- 字面量类型是指一个抽象类型中更具体固定的子类型(值),比如“string”类型中的“hello world”它是一个具体值也是一个固定类型,“hello tom”不等于“hello world”,也就意味着“hello tom”不是“hello world”字面量类型。
- 目前ts中有三种可用的字面量类型集合:字符串字面量类型集合、数字字面量类型集合、布尔字面量类型集合
- 字面量类型通过type关键字或直接拿一个集合放在类型位置上定义。
type Easing = "ease-in" | "ease-out" | "ease-in-out";
type Size = 8 | 16 | 32;
type Disabled = true;
interface MapConfig {
lng: number;
lat: number;
tileSize: 8 | 16 | 32;
}
联合类型和交叉类型(Unions and Intersection Types)
联合类型
联合类型表示可以使用符合列举的几种类型之一的数据(或关系)
// 字符串或者数字
function padLeft(value: string, padding: string | number) {
// ...
}
交叉类型表示可以使用同时符合列举的几种类型的数据(与关系)
interface ErrHandling {
success: boolean;
error?: { message: string };
}
interface ArtworksData {
artworks: { title: string }[];
}
interface ArtistsData {
artists: { name: string }[];
}
// 合并类型(需要既具有Data接口定义的属性,又需要有handling定义的属性)
type ArtworksResponse = ArtworksData & ErrHandling;
type ArtistsResponse = ArtistsData & ErrHandling;
const handleArtistsResponse = (response: ArtistsResponse) => {
// ...
}
类
- 基于类的面向对象编程方式
class Greeter {
// 属性
greeting: string;
// 构造函数
constructor(message: string) {
this.greeting = message;
}
// 方法
greet() {
// this表示访问类的成员
return "Hello" + this.greeting;
}
}
// 构造实例
let greeter = new Greeter("world");
继承
- 使用继承扩展现有的类
例1:
// 基类(超类)
class Animal {
move(distanceInMeters: number = 0) {
console.log("Animal moved ", distanceInMeters);
}
}
// 继承animal类 派生类(子类)
class Dog extends Animal {
bark() {
console.log("woof!");
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
例2:
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log("move", distanceInMeters);
}
}
class Snake extends Animal {
constructor(name: string) {
// 以name入参,执行基类的构造方法(必须的)
super(name);
}
// 重写基类的move方法
move(distanceInMeters: number = 0) {
console.log("Slithering");
// 执行基类的move方法(基类:当作函数时会执行构造函数;当作对象时可获取属性和方法)
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) {
console.log("Galloping");
super.move(distanceInMeters);
}
}
类成员修饰符
- 这些修饰符修饰的成员都是当类被实例化时才会被初始化的属性,反过来讲是依附于实例存在的属性(区别于静态属性)
修饰符 | 含义 | 兼容比较 |
---|---|---|
public(默认) | 可被自由访问 | 不同声明处,但类型相同则兼容 |
private | 可以在声明它的类外部访问 | 相同声明处,且类型相同则兼容 |
protected | 可以在声明它的类和派生类中访问 | 相同声明处,且类型相同则兼容 |
readonly | 只读属性,表示必须在声明时或构造函数里被初始化 | - |
例1: 默认修饰符public
class Animal {
public name: string;
public constructor(theName: string) {
this.name = theName;
}
public move(distanceInMeters: number) {
console.log("move", distanceInMeters);
}
}
例2: private修饰的属性不可在其类外访问
class Animal {
// 类的私有属性相当于只局限于该类内部自己使用的属性
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
// err!私有属性不可在声明它的类外使用
new Animal("cat").name;
例3: 不同声明,相同类型的private修饰类型不兼容
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Rhino extends Animal {
constructor() {
super("Rhino");
}
}
class Employee {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
// 继承了基类的子类和基类兼容
animal = rhino;
// 结构相同但属性不是同一处声明的不兼容
animal = employee; // err! Animal 与Employee不兼容
例4: protected成员与private类似,但是多了仍可以在派生类中访问到的性质
class Person {
// 基类中声明的保护类型
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
// 派生类中仍可以访问到基类中的保护类型成员
return `Hello, my name is ${this.name}. I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
// err! 不可在基类和派生类外访问到保护成员
console.log(howard.name);
例5: 构造函数被标记为protected,意味着该类不可被实例化,但可被继承(不可在类外访问执行,但可在派生类中访问执行)
class Person {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
// 可在派生类中调用基类中保护类型的构造函数
super(name);
this.department = department;
}
public getElevatorPitch() {
return this.name + this.department;
}
}
let howard = new Employee("Howard", "Sales");
// err!不可在类外执行保护类型的构造函数
let john = new Person("John");
例6: 只读属性只能在声明时或构造函数里被初始化
class Octopus {
readonly name: string;
// 声明时初始化
readonly numberOfLegs: number = 8;
constructor (theName: string) {
// 构造函数里初始化
this.name = theName;
}
}
let tom = nem Octopus('Tom');
// err! 只读属性
tom.name = "Tim";
参数属性
给构造函数的参数增加成员修饰符实现直接对成员的声明&初始化
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
存取器
- getters/setters:截取对对象成员的访问和赋值
- 只有get无set的存取器被自动推断为readonly
给普通类加上存取器的使用,使得获取和赋值操作必须经过中间动作
// ---普通类
class Employee {
salary: number;
}
let tom = new Employee();
// 普通类成员可以被随意访问修改
tom.salary = 2500;
console.log(tom.salary);
// ---加入存取器
let password = '123';
class Employee {
private _salary: number;
get salary(): string {
return this._salary + '元';
}
set salary(newSalary: number) {
if (password && password === '123') {
this._salary = newSalary;
} else {
console.log("err");
}
}
}
let employee = new Employee();
employee.salary = 2500;
alert(employee.salary);
静态属性
静态属性只存在于类本身身上,而不是类的实例上,访问时直接以访问类对象的属性的形式访问
class Person {
static eyes = 2;
static legs = 2;
getPersonFeature() {
return Person.eyes + Person.legs;
}
constructor(public name: string) { }
}
let tom = new Person('Tom');
console.log(tom.getPersonFeature());
抽象类
- 抽象类作为其他派生类的基类使用,一般不会被直接实例化(类的基础模板类)
- 不同于接口,抽象类内部可以包含非抽象成员的实现细节
- 通过abstract定义抽象类和抽象方法
- 抽象方法不包含具体实现,必须在派生类中实现
抽象类举例
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('move');
}
}
泛型(Generics)
- 为了支持组件重用:一个组件支持多种类型的数据
泛型变量
- 泛型变量用来指代数据的类型。它是一种特殊的变量,指类型而不是指值;用 <> 表示,在做定义时候使用
定义一个泛型函数
// <T> 声明了一个泛型变量T,然后在后面的类型表示中就可以使用它了
function identity<T>(arg: T): T {
return arg;
}
// 可当作类型描述中的一部分使用
function identity<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
定义一个泛型接口
interface GenericIdentityFn {
<T>(arg: T): T;
}
// 接口内共享同一个泛型变量
interface GenericIdentityFn<T> {
(arg: T): T;
myArr: T[];
}
定义一个泛型类
- 泛型类指的是实例部分的类型,所以静态属性不能用泛型。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let emtity = new GenericNumber<number>();
泛型约束
- 泛型作为类型的指代,故也可以理解泛型和接口可以组合使用,用来表示具有某些特定属性的泛型(泛型约束)
下例展示了限制只可接受带有length属性的数据(允许带有length属性的数据类型)
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
枚举
- 使用枚举可以定义一组带名字的常量
- ts支持数字和基于字符串的枚举
- 使用enum进行定义
数字枚举
数字枚举具有自增长的性质, 会依据上一个成员的值自增得到下一个成员的值(默认第一个是0);并且可以反向映射,由枚举值得到枚举名字
enum Direction {
Up = 1,
Down, // 2
Left, // 3
Right // 4
}
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
function act(move: number, direction: Direction): void {
console.log(move, direction);
}
act(10, Direction.Up);
// 无初始化的成员要么在首位,要么在明确被数值初始化了的成员之后
enum E {
A = getSomeValue(),
B, // Error!
}
字符串枚举
字符串枚举中的每个成员都必须进行显式的初始化
enum status {
SUCC = "成功",
FAIL = "失败",
}
const枚举
- const枚举(常量枚举)不允许包含计算成员
const enum Directrions {
Up,
Down,
Left,
Right
}
外部枚举
- 外部枚举用来描述已经存在的枚举的形状
- 外部枚举中无初始化方法时被当作需要进过计算的(区别于正常枚举中是当作常量成员)
declare enum Enum {
A = 1,
B,
C = 2
}
变量声明
- let和const是es6规范中新增的变量声明语法,因为ts是js的超集,所以其本身就支持二者,并且推荐在ts中使用。
var,let,const
- 下面简要介绍一下三种变量声明的特点。
关键字 | 作用域 | 说明 | 声明 |
---|---|---|---|
var | var作用域/函数作用域 | 在包含它的函数,模块,命名空间或全局作用域内任何文职被访问到 | 可重复声明覆盖 |
let | 词法作用域/块作用域 | 变量在包含它们的块或for循环之外不能访问 | 不能在声明之前读写,不可多次声明 |
const | 词法作用域/块作用域 | 变量在包含它们的块或for循环之外不能访问 | 不能在声明之前读写,不可多次声明,不可重复赋值 |
解构
-
解构数组
let arr = [1, 3]; let [first, second] = arr; let [first, second] = [second, first]; function fun([first, second]: [number, number]) { console.log(first, second); } fun(arr); let arr2 = [1, 2, 3, 4]; let [a, ...rest] = arr2; let [, b, , d] = arr2;
-
解构对象
let obj = { a: 1, b: 2, c: 3, }; let { a, b } = obj; let { first, ...rest } = obj; let { a: aaa, b: bbb } = obj; let { a: val1, b: val2 }: { a: number, b: number } = obj; function fun(obj: {a: number, b?: number}) { let { a, b = 2 } = obj; return a + b; }
-
函数声明
type C = { a: string, b?: number }; function fun({ a, b }: C): void { console.log(a, b); }; // 为参数对象属性设置默认值(无该属性时生效) function fun2({ a = "", b = 0 }: C): void { console.log(a, b); }; // 为参数对象设置默认对象(无该对象时生效) function fun3({ a, b = 0 } = { a: "" }: C): void { console.log(a, b); };
-
展开
- 对象,数组等(可迭代对象)可展开
- 展开会覆盖前面的同名属性
- 仅包含对象自身的可枚举属性(会丢失方法)
- ts中不允许展开泛型函数上的类型参数