类型基础
强类型
不允许改变变量的数据类型,除非进行强制类型转换 (Java,C#,Python,C/C++)
弱类型
变量可以被赋予不同的数据类型 (PHP,JavaScript)
动态类型
在执行阶段确定所有变量的类型 (Python)
静态类型
在编译阶段确定所有变量的类型 (Java,C/C++,C#)
基础类型
- number
- string
- boolean
- Array
- Tuple
- function
- object
- symbol (独一无二的值)
- void (没有类型)
- any (任意类型)
- undefined (未定义)
- null (没有值)
- never (永远不存在的值) (1.抛异常 2.死循环)
枚举类型
枚举:一组具有名字的常量集合
数字枚举
- 枚举成员值默认从0递增,也可自定义,支持反向映射
// 数字枚举 enum Role{ Reporter, Developer, Maintainer, Owner, Guest } console.log(Role.Reporter) // 0
字符串枚举
- 不支持反向映射
// 字符串枚举 enum Message { Success = '成功', Error = '失败', } console.log(Message);
异构枚举
- 字符串枚举和数字枚举混用
// 异构枚举 enum Anser{ N, Y='yes' }
枚举成员
- 拥有只读属性
- 1.const member(常量枚举,在编译阶段被计算出结果)
- 1.无初始值
- 2.对常量成员的引用
- 3.常量表达式
- 2.computed member(计算枚举,表达式保留到程序的执行阶段)
- 非常量表达式
enum Char { // 常量枚举 a, // 无初始值 b = Char.a, // 对常量成员的引用 c = 1 + 3, //常量表达式// // computed member 执行阶段(运行时) (computed member 后面的成员需要赋予初始值) d = Math.random(), // (执行阶段,才会计算) e = '123'.length, // (执行阶段,才会计算) f = 4, //需要赋予初始值 }
常量枚举
使用const的声明的枚举就是常量枚举
- 编译后被移除
- 成员只能为 const member(常量枚举)
const enum Month { Jan, Feb, Mar, } let mounth = [Month.Jan, Month.Feb, Month.Mar]; console.log(mounth); // [0,1,2]
枚举/枚举成员作为类型
- 1.无初始值
- 2.枚举成员均为数字
- 3.枚举成员均为字符串
enum E {a,b,} //无初始值 enum F {a = 0,b = 1,} //均为数字 enum G {a = 'apple', b = 'banana',} //均为字符串
接口
对象类型接口
// 对象类型接口
interface List {
readonly id: number;// 只读属性
name: string;
}
interface Result {
data: List[];
}
function render(result: Result) {
result.data.forEach(item => {
console.log(item);
});
}
// 将对象字面量赋值给变量
let result = {
data: [
{id: 1, name: 'a', sex: 'male',}, // 鸭式辩型法
{id: 2, name: 'b', age: 10,},
],
};
render(result);
- 检查原则:鸭式辩型法 (动态语言风格)
-
绕过对象字面量检查
- 1.将对象字面量赋值给变量
- 2.使用类型断言
// 类型断言一 (推荐使用这种) render({ data: [ { id: 1, name: 'a', sex: 'male' }, { id: 2, name: 'b' }, ], } as Result); // 类型断言二( 在react会引起歧义) render(<Result>{ data: [ { id: 1, name: 'a', sex: 'male' }, { id: 2, name: 'b' }, ], });
- 3.使用字符串索引签名
interface List { readonly id: number; name: string; [x: string]: any; //字符串索引签名 用任意的字符串去索引List,可以得到任意的结果,这样List就支持多个属性 }
-
对象的属性
- 1.可选属性
- 2.自读属性
interface List { readonly id: number;// 只读属性 name: string; age?: number; //可选属性 ?表示可有可无 }
-
可索引类型接口
- 数字索引
// 数字索引 interface StringArray { [index: number]: string; //用任意的数字索引StringArray,都会得到string(相当于声明了一个字符串类型的数组) } let chat: StringArray = ['A', 'B']; console.log(chat); //["A", "B"]
- 字符串索引
// 字符串索引接口 interface Names { [x: string]: string; //用任意的字符串去索引Names,都会得到string // y: number; //不被允许 [z: number]: string; //用任意的数字索引Names,都会得到string } let str: Names = { a: 'c', 2: 'b' }; console.log(str);//{2: "b", a: "c"}
函数类型接口
interface H {
(arg: type): type;
}
// 类型别名 使用type
type Add = (x: number, y: number) => number; // 跟上面二者等价
let add:Add=(a,b)=>a+b;
混合类型接口
一个接口既可以定义一个函数,也可以像对象一样,有属性和方法
interface H {
(arg:type): type; //定义一个函数
prop: type;//定义属性
method(arg:type): type;//定义方法
}
// 示例
interface Lib {
(): void;
version: string;
doSomething(): void;
}
function getLib() {
let lib: Lib = (() => {}) as Lib; // 类型断言
lib.version = '1.0';
lib.doSomething = () => {};
return lib;
}
let lib1 = getLib();
let lib2 = getLib();
console.log(lib1 === lib2);// false
类类型接口
- 类必须实现接口中的所有属性
- 接口只能约束类的公有成员,不能约束私有成员,受保护成员,静态成员和构造函数
// 接口
interface Human {
// new (name: string): void; // 接口不能约束构造函数
name: string;// 接口只能约束类的公有成员
eat(): void;
}
// 类继承接口
// 类实现接口的时候,必须实现接口中声明的所有属性(接口属性为公用成员)
class Asian implements Human {
constructor(name: string) {
this.name = name;
}
name: string;
eat() {}
sleep() {}// 可以定义自有方法
}
接口继承接口
- 抽离可复用的接口
- 将多个接口整合成一个接口
interface Human {
name: string;
eat(): void;
}
// 继承Human
interface Man extends Human {
run(): void;
}
// 单独定义Child接口
interface Child {
cry(): void;
}
// Boy接口继承多个接口
interface Boy extends Man, Child {}
// 定义一个对象,必须需要包含四个属性,分别来自Human,Man, Child
let boy: Boy = {
name: '',
run() {},
eat() {},
cry() {},
};
接口继承类
- 抽象出类的公有成员,私有成员和受保护成员 成员结构
// 类
class Auto {
state = 1; // 公共属性
}
// 当接口继承了一个类类型时,它会继承类的成员结构但不包括其实现
// 接口继承类
interface AutoInterface extends Auto {
// AutoInterface接口隐含了state属性
select(): void;
}
// 类继承接口
class C implements AutoInterface {
state = 2;
}
// 子类
// 当一个接口继承了一个拥有私有或受保护的成员的类时,只能被这个类或其子类所继承
class Bus extends Auto implements AutoInterface {
select() {}
}
函数
定义函数
- 定义方式
定义函数类型,无函数体
- function
- 变量定义
- 类型别名
- 接口定义
// 函数定义 function add1(x: number, y: number) { return x + y; } //变量定义 let add2: (x: number, y: number) => number; // 类型别名 type add3 = (x: number, y: number) => number; // 接口定义 interface add4 { (x: number, y: number): number; }
- 类型要求
- 参数类型必须声明
- 返回值类型一般无需声明
函数参数
- 参数个数
- 实参和形参必须一一对应
- 可选参数
- 必选参数不能位于可选参数后面
// 可选参数 必须位于必选参数之后 function add5(x: number, y?: number) { return y ? x + y : x; }
- 默认参数
- 在必须参数前,默认参数不可省略(可使用undefined,来获取默认参数值)
// 默认值 function add6(x: number, y = 2, z: number, q = 1) { console.log(y); // 2 return x + y + z + q; //1+2+3+1=7 } console.log(add6(1, undefined, 3));//可使用undefined,来获取默认参数值
- 在必须参数后,默认可以省略
- 剩余参数
function add7(x: number, ...rest: number[]) {
return x + rest.reduce((pre, cur) => pre + cur);
}
console.log(add7(1, 2, 3, 4, 5));//15
函数重载
静态语言:函数的名称相同,参数的个数或类型不同 TypeScript:预先定义一组名称相同,类型不同的函数声明,并在一个类型最宽泛的版本中实现重载
// 先定义类型不同的函数声明
function add8(...rest: number[]): number;// 把最容易匹配的写在最前面
function add8(...rest: string[]): string;
// 在一个类型最宽泛的版本中实现重载
function add8(...rest: any[]): any {
let first = rest[0];
if (typeof first === 'string') {
return rest.join('');
} else if (typeof first === 'number') {
return rest.reduce((pre, cur) => pre + cur);
}
}
console.log(add8(1, 2, 3)); //6
console.log(add8('a', 'b', 'c')); //abc
类
基本实现
类中定义的属性都是实例属性,类中定义的方法都是原型方法 实例属性必须有初始值,或在构造函数中被赋值,或为可选成员
class Dog {
// 构造参数添加类型注解
constructor(name: string) {
this.name = name; // 实例属性必须有初始值,或在构造函数中被赋值
}
name: string; //实例属性 (成员属性 添加类型注解)
run() {} // 原型方法
}
继承
子类的构造函数中必须包含super调用
// 类的继承
class Husky extends Dog {
constructor(name: string, public color: string) {
super(name); // super表示父类的实例
this.color = color;
}
}
成员修饰符
- public
- 对所有人可见,所有成员默认为public
- private
- 只能在被定义的类中访问,不能通过实例或子类访问
- private constructor:不能被实例化,不能被继承
- protected
- 只能在被定义类和子类访问,不能通过实例访问
- protected constructor:不能被实例化,只能被继承,相当于声明了一个基类
- readonly
- 必须有初始值,或者构造函数中被赋值
- static
- 只能有类名调用,不能通过实例访问,可继承
class Dog {
// private constructor:不能被实例化,不能被继承
// protected constructor:不能被实例化,只能被继承,相当于声明了一个基类
constructor(name: string) {
this.name = name;
}
//显式声明
public name: string = 'dog'; // 公有成员(实例属性)
private pri() { // 私有成员 可被类访问 不可被子类和实例调用
console.log('pri');
}
protected pro() { // 受保护成员 可以在类和子类访问,但不可在实例中访问
console.log('pro');
}
readonly legs: number = 4;//只读属性 不能被更改,一定要被初始化
run() {}
// 静态成员 只能通过类名来调用,不可通过实例来调用.可被子类继承和调用
static food: string = 'bones';
}
构造函数参数中的修饰符
- 将参数变为实例属性
class Dog3 { // 将参数变为实例属性 constructor(public name: string) { this.name = name; } }
抽象类
- 不能被实例化,只能被继承
- 抽象方法包含具体实现
- 子类直接复用
- 抽象方法不包含具体实现
- 子类必须实现
- 抽象方法包含具体实现
// 抽象类
abstract class Animal {
// 抽象方法包含具体实现
eat() {
console.log('eat');
}
// 抽象方法只能出现在抽象类中,抽象方法不包含具体实现
abstract sleep(): void; // 方法签名必须在派生类中实现
}
// 无法创建抽象类的实例
// let animal = new Animal();
class Dog extends Animal {
constructor(name: string) {
super();
this.name = name;
}
name:string
run() {}
sleep() {
console.log('dog sleep');
}
}
let dog = new Dog('wangwang');
dog.eat();// eat 子类直接复用
- 多态
- 多个子类对父抽象类的方法有不同实现,实现运行时绑定
class Cat extends Animal {
constructor() {
super();
}
// 多个子类对父抽象类的方法有不同实现,实现运行时绑定
sleep() {
console.log('cat sleep');
}
}
let cat = new Cat();
cat.sleep(); // cat sleep
this类型
- 实现实例方法的链式调用
- 在继承时,具有多态性,保持父子类之间接口调用的连贯性
// 方法的链式调用
class WorkFlow {
step1() {
return this;
}
step2() {
return this;
}
}
// 实例方法的链式调用
new WorkFlow().step1().step2();
class MyFlow extends WorkFlow {
next() {
return this;
}
}
//在继承时,具有多态性,保持父子类之间接口调用的连贯性
new MyFlow().next().step1().next().step2();
泛型
支持多种类型的方法
- 函数重载
function log(value:string):string; function log(value:string[]):string[]; function log(value: any) { console.log(value); return value; }
- 联合类型
function log(value: string|string[]):string|string[] { console.log(value); return value; }
- any类型
- 丢失类型约束
function log(value: any) { console.log(value); return value; }
- 泛型
- 不预先确定的类型,使用时才确定
泛型:不预先确定的数据类型,具体的类型在使用时的时候才能确定
泛型函数
- 定义
- function generic(arg:T):T
- 调用
- generic(arg) 指明调用类型,添加类型参数
- generic(arg) 类型推断,省略类型参数(推荐)
function log<T>(value: T): T { console.log(value); return value; } // 调用方式 // 指明调用类型,添加类型参数 log<string[]>(['a', 'b']); // 类型推断,省略类型参数(推荐) log(['a', 'b']);
- 泛型函数类型
- type Generic=(arg:T)=>T
type Log=<T>(value:T)=>T let myLog:Log=function(value){ console.log(value); return value; }
泛型接口
- 定义
- interface Generic{(arg:T):T}
- 实现
- let generic:Generic(必须制定类型)
interface Log<T> {
(value: T): T;
}
let myLog: Log<number> = log;
myLog(1);
// 指定默认类型
interface Log<T=string> {
(value: T): T;
}
泛型类
- 定义
- class Generic{ method(value:T){}}
- 泛型不能应用于类的静态成员(static)
- 实例化
- let generic = new Generic()
- let generic = new Generic(),T可为任意类型
class Log<T> {
public run(value: T) {
console.log(value);
return value;
}
}
let log1 = new Log<number>(); // 显示的设置类型参数
let log2 = new Log(); //
泛型约束
- T extends U(T必须具有U的属性)
// 泛型约束
interface Length {
length: number;
}
// 输入的参数,必须要有length属性
function logfn<T extends Length>(value: T): T {
console.log(value, value.length);
return value;
}
logfn([1, 2, 3]);
logfn({ length: 2 });
- 函数和类可以轻松的支持多种类型,增强程序的扩展性
- 不必写多条函数重载,冗长的联合类型声明,增强代码可读性
- 灵活控制类型之间的约束
类型注解
作用:相当于强类型语言中的类型声明 语法:(变量/函数):type
类型检查机制
类型检查机制: TypeScript编译器在做类型检测时,所秉承的一些原则,以及表现出的一些行为. 作用:辅助开发,提供开发效果
- 类型推断
- 类型兼容性
- 类型保护
类型推断
- 含义:根据某些规则自动的为变量推断出类型
- 基础类型推断
- 初始化变量
let a=1;
- 设置函数默认参数
let c=(x=1)=>{};
- 确定函数返回值
let c=(x=1)=>x+1;
- 最佳通用类型推断
- 推断出一个可以兼容当前所有类型的通用类型
let b=[1,null]
- 上下文类型推断
- 根据事件绑定推断出事件类型
window.onkeydown = (event) => { console.log(event.button); }
类型断言
- 含义:用自己声明的类型覆盖类型推断
- 方式:表达式 as type
- 表达式
- 弊端:没有按照接口的约定赋值,不会报错
// 定义接口
interface Foo {
bar: number;
}
var foo = {} as Foo; // 类型断言
foo.bar = 1;
// 等价于
// 声明时,指定对象类型
var foo: Foo = {
bar: 1,
};
类型兼容性
- 含义:当一个类型Y可以被赋值给另一个类型X时,可以说类型X兼容类型Y(小兼容大)
- X(目标类型)=Y(源类型),则X兼容Y
// 接口兼容性 interface X{ a:number; b:number; } interface Y{ a:number; b:number; c:number; } let x:X={a:1,b:2} let y:Y={a:1,b:2,c:3} x=y // 满足 // y=x // 不满足
口诀: 结构之间兼容:成员少的兼容成员多的 函数之间兼容:参数多的兼容参数少的
-
接口兼容性
- 成员少的兼容成员多(鸭式辨型法)
-
函数兼容性
- 参数个数:目标函数多于源函数
// 函数兼容性 // 目标函数 type Handler=(a:number,b:number)=>void function hof(hander:Handler){ return hander } //1) 参数个数 // 源函数 let handerl=(a:number)=>{} hof(hander1) // 可以 let hander2=(a:numer,b:number,c:number)=>{} hof(hander2) //不可以
- 可选参数和剩余参数,遵循原则
- 固定参数兼容可选参数和剩余参数
- 可选参数不兼容固定参数和剩余参数(严格模式)
- 剩余参数兼容固定参数和可选参数
// 可选参数和剩余参数 let a=(p1:number,p2:number)=>{} let b={p1?:number,p2?:number}=>{} let c={...args:number[]}=>{} a=b // 兼容 a=c // 兼容 b=c // 不兼容 b=a // 不兼容 c=a // 兼容 c=b // 兼容
- 参数类型:必须匹配
- 参数为对象
- 严格模式: 成员多的兼容成员少的
- 非严格模式:相互兼容(函数参数双向协变)
// 参数类型 let handler3 = (a:string)=>{} hof(handler3) //不兼容 // 3D兼容2D interface Point3D { x: number; y: number; z: number; } interface Point2D { x: number; y: number; } let p3d = (point: Point3D) => {}; let p2d = (point: Point2D) => {}; p3d = p2d; // 兼容 p2d = p3d; //不兼容
- 返回值类型:目标函数必须与源函数相同,或为其子类型(成员少兼容成员多)
let f = () => ({ name: 'a' }); let g = () => ({ name: 'a', location: 'g' }); f = g; //兼容 成员少的兼容成员多的 g = f; //不兼容
-
枚举兼容性
- 枚举类型和数字类型互相兼容
- 枚举类型之间不兼容
enum Fruit { apple, banana, } enum Color { red, yellow, } let fruit: Fruit.apple = 3; // 兼容 let no: number = Fruit.apple; // 兼容 let color: Color.red = Fruit.apple;//不兼容
-
类兼容性
- 静态成员和构造函数不在比较范围
- 两个类具有相同的实例成员,它们的实例相互兼容
- 类中包含私有成员或受保护成员,只有父类和子类的实例相互兼容
class A { constructor(p: number, q: number) {} id: number = 1; } class B { static s = 1; constructor(p: number) {} id: number = 1; } let aa = new A(1, 2); let bb = new B(1); aa=bb; //兼容 静态成员和构造函数不在比较范围 bb=aa; //兼容 // 带有私人成员则互不兼容 class A { constructor(p: number, q: number) {} id: number = 1; private name: string = ''; } class C extends A {} // 父类和子类 实例 可以互相兼容 let cc = new C(1, 2); aa = cc; // 兼容 cc = aa; // 兼容
-
泛型兼容性
- 泛型接口:只有类型参数T被接口成员使用时,才会影响兼容性
interface Empty<T> { } let obj1: Empty<number> = {}; let obj2: Empty<string> = {}; obj1=obj2;// 兼容 interface Empty<T> { value:T } let obj1: Empty<number> = {}; let obj2: Empty<string> = {}; obj1=obj2;// 不兼容
- 泛型函数:定义相同,没有指定类型参数时就兼容
let log1 = <T>(x: T): T => { return x; }; let log2 = <U>(y: U): U => { return y; }; log1 = log2;
类型保护
- 定义:在特定的区块中保证变量属于某种特定的类型
- 创建区块的方法:
if ((lang as Java).helloJava) { (lang as Java).helloJava(); } else { (lang as JavaScript).helloJavaScript(); }
- instanceof
// 添加 instanceof if (lang instanceof Java) { lang.helloJava(); } else { lang.helloJavaScript(); }
- typeof
// typeof 判断基本类型 if (typeof x === 'string') { x.length; } else { x.toFixed(); }
- in
// 使用 in,对类添加属性 if ('java' in lang) { lang.helloJava(); } else { lang.helloJavaScript(); }
- 类型保护函数
- 特殊的返回值:arg is type(类型谓词)
function isJava(lang: Java | JavaScript): lang is Java { //类型谓词 return (lang as Java).helloJava !== undefined; //类型断言 }
- instanceof
高级类型
交叉类型(类型并集)
- 含义:将多个类型合并为一个类型,新的类型将具有所有类型的特征
- 应用场景:混入
// 接口 interface DogInterface { run(): void; } interface CatInterface { jump(): void; } // 交叉类型取并集 适合做混入 let pet: DogInterface & CatInterface = { run() {}, jump() {}, };
联合类型(类型交集)
- 含义:类型并不确定,可能为多个类型的一个
- 应用场景:多类型支持
- 可区分的联合类型:结合联合类型和字面量类型的保护方法
// 联合类型 可以为多个类型的一个,类型不确定性,增强代码的灵活性 let a: number | string = 'a'; // 对象的联合类型 class Dog implements DogInterface { run() {} eat() {} } class Cat implements CatInterface { jump() {} eat() {} } enum Master { Boy, Girl, } function getPet(master: Master) { let pet = master === Master.Boy ? new Dog() : new Cat(); pet.eat(); // 可以访问,pet拥有Dog和Cat共同的方法eat pet.run(); // 不可访问 return pet; } // 接口通过公共的属性,创建类型保护区块 interface Square { kind: 'square'; size: number; } interface Rectangle { kind: 'rectangle'; width: number; height: number; } interface Circle { kind: 'circle'; r: number; } // 创建联合类型 type Shape = Square | Rectangle | Circle; // 1)指定明确的返回值类型 // function area(shape: Shape): number { function area(shape: Shape) { // 通过公共的属性,创建类型保护区块 switch (shape.kind) { case 'square': return shape.size * shape.size; case 'rectangle': return shape.width * shape.height; case 'circle': return Math.PI * shape.r * 2; default: // 2)利用throw,抛出错误 return ((e: never) => { throw new Error(e); // 抛出接口 异常错误 })(shape); } }
字面类型
- 字符串字面量
// 字面量的联合类型 let b: 'a' | 'b' | 'c';
- 数字字面量
// 数字的联合类型 let c: 1 | 2 | 3;
- 应用场景:限定变量取值范围
索引类型
let obj = {
a: 1,
b: 2,
c: 3,
};
function getValues(obj: any, keys: string[]) {
return keys.map(key => obj[key]);
}
console.log(getValues(obj, ['a', 'b']));//[1,2]
console.log(getValues(obj, ['e', 'f']));//[undefined,undefined]
- 要点:
- keyof T(索引查询操作符):类型T公共属性名的字面量联合类型
// 索引类型的查询操作符 keyof T interface Obj { a: number; b: string; } let key: keyof Obj; //key是Obj 所有属性名的联合类型 // 等同于 let key:'a'|'b';
- T[K] (索引访问操作符):对象T的属性K所代表的类型
// T[K] 索引访问操作符 let value: Obj['a']; // 等同于 let value: number
- 泛型约束
// 添加类型约束,保证key都在obj中 // 泛型函数 // T extends U 继承 function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] { return keys.map(key => obj[key]); } console.log(getValues(obj, ['e', 'f']));// 报错
- 应用场景:从一个对象中选取某些属性的值
映射类型
- 含义:从旧类型创建出新类型
- 应用场景:
- Readonly:将T的所有类型变为只读
interface Obj { a: string; b: number; c: boolean; } type ReadonlyObj = Readonly<Obj>; // 等同于 type ReadonlyObj = { readonly a: string; readonly b: number; readonly c: boolean; }
- Partial:将T的所有属性变为可选
type PartialObj = Partial<Obj>; // 等同于 type PartialObj = { a?: string | undefined; b?: number | undefined; c?: boolean | undefined; }
- Pick:选取以K为属性的对象T的子集
type PickObj = Pick<Obj, 'a' | 'b'>; // 等同于 type PickObj ={ a: string; b: number; }
- Record<K,T>:创新属性为K的新对象,属性值的类型为T
type RecordObj = Record<'x' | 'y', Obj>; // 等同于 type RecordObj = { x : Obj; y : Obj; }
条件类型
- 含义:T extends U ?X:Y(如果类型T可以赋值给类型U,那么结果类型就是X,否则就是Y)
- 应用场景:
- Exclude<T,U>:从T中过滤掉可以赋值给U的类型
type T = Exclude<'a' | 'b' | 'c', 'a' | 'e'>; // 取T中的不同项 // 等同于 type T = "b" | "c"
- Extract<T,U>:从T中抽取可以赋值给U的类型
// 从T中抽取出符合类型U的类型 type T8 = Extract<'a' | 'b' | 'c', 'a' | 'e'>; // 取T的相同项 // 等同于 type T8 = "a"
- NonNullable:从T中出去undefined和null
type T7 = NonNullable<string | number | undefined | null>; // 等同于 type T7 = string | number
- ReturnType:获取函数的返回值类型
// 获取函数返回值的类型 type T9 = ReturnType<() => string>;