类和继承
类修饰符
abstract ,不能new,只能被子类实现
属性访问符
private protected public abstract readonly
类多态
- 子类继承父类,重写父类的方法
- 子类方法的访问修饰符要大于父类
- ==参数和返回是否有要求?==
属性定义未初始化报错处理
// 方案1
class A {
construtor(public a:number)
}
// 方案2 ,叹号,ts4以上
class A{
public a!: number
}
// 方案3
class A{
a:string;
constructor(a:string){
this.a = a
}
}
js实现
class A extends B { }
// 两方面继承,原型和静态
A.__proto__ === B
A.prototype.__proto__ === B.prototype
//js实现 (组合寄生)
// 静态继承
function extendsStatics(Son, Parent) {
for (let k in Parent) {
if (Object.prototype.hasOwnProperty.call(Parent, k)) {
Son[k] = Parent[k];
}
}
}
// 原型继承
function extendFn(Son, Parent) {
extendsStatics(Son, Parent); // 静态
function Middle() {
this.constructor = Son; // 重要 Son.prototype.constructor= Son
}
Middle.prototype = Parent.prototype;
Son.prototype = new Middle(); // 原型继承寄生组合
}
// class B
function B(age) {
this.age = age;
}
B.prototype.getAge = function () {
return this.age;
};
// class A
function A(name, age) {
this.name = name;
B.call(this, age);
}
extendFn(A, B);
A.prototype.afn=()=>{}
// A.__proto__ = B //没法new实现,new 的话就是,xx.__proto__==B.prototype
// Object.create解析
Object.create = function(prototype){
function Middle(){
// 没有this.constructor = xx
}
Middle.prototype = prototype
return new Middle()
}
A.prototype = Object.create(B.prototype)
A.prototype.constructor = A
// A.prototype.__proto__ === B.prototype
类型注解
基础类型
- number
- string
- boolean
- symbol
- undefined
- null tips:
- strictNullChecks未开启时,null,undefined可以赋值给所有类型,除了never
其他类型
- any, 所有类型的父类型,所有类型的子类型,可以调用任何方法属性,跳过检查
- unkown,所有类型的父类型,但不是所有类型的子类型,不能调任意属性
- never,一个在逻辑上不会出现的类型
- enum, 如果值时数字类型,则会双向映射,否则不会
- void
// never
function fn():never{ throw new Error() }
function fn(param: string){
if(typeof param === 'string'){
// param被推断为string
}else{
// param被推断为never
}
}
// 枚举
enum Status {
on = 0,
off
}
// 编译为,数字值类型双向映射
const Status={}
Status[Status[0]='on']=0
引用类型
- object
- {} , 等价于Object类型 ,只是没有Object类型的方法
- 数组
- 元组
- 函数类型
// 元组
// 标签
type ArrType = [name:string, age:number]
// 可变元组
type ArrType = [name:string, age:number,...rest:any[], stu:string]
// 默认推导
const arr = [1 , '2'] //被推导成 string|number[] ,而不是元组
// 函数类型定义的几种方式
function fn(param: string):string{}
type Fn = (param: string)=> string
interface Fn{
(param: string):string
}
// 方法定义
abstract class List2 {
abstract add(param: string):string
put(param:string):string{ return '' }
}
type Tp = {
fn(param:any):string
}
interface Tp2{
fn(param:any):string
}
装箱类型
- Object
- String
- Function
- Boolean
- Number
- Symbol
tip
- 装箱类型接受的值,可以接受所有的拆箱类型的值,而且比拆箱类型多了些方法属性的定义
- object只能接收对象或null ; 而Object类型还可接收基础类型
字面量类型(值类型)
只读
- const arr = [1,2] as const //子元素不能赋值,as const后变成==值类型==了
- let arr : readonly string[] = ['']
类型断言(类型转换)
两种语法
- o as any
- o (tsx|jsx不能用)
什么情况下可以类型断言
- any 和 unkown是所有类型的父类,和任何类型都可以相互断言
- 类implements接口,类extends父类 , 父子类可以相互断言(对于类,接口,type定义的接口)
- 没有继承关系,就是鸭子变形法,判断一个是不是另一个的子集(可选属性忽略,不进行判断;==对于类,不能两个类,同时有private,protected修饰符,只允许一个类有==),如果是则可以相互断言
- 联合类型A|B, 和其子类型A或B可以相互断言 ,3值类型属于number子类型,也就属于number|string的子类型
- 交叉类型 ,T & U是T & U & V 的子集,可以相互断言
- 空对象值类型{},是任何对象类型的子类型,可以相互断言
// 联合类型
const a: 3 = 3;
type X = string | number;
(a as X).toString()
// 可选属性忽略
type A = {
a: string;
b?: number; // 可选属性忽略
};
type B = {
a: string;
c: number;
};
const a: A = { a:''};
(a as B).c
// 类修饰符private ,protected
// 不能两个类,同时有private,protected修饰符,只允许一个类有,这样才是一个类是另一个类的子集,因为private和protected分别只能在自己类或子类中使用
class A {
constructor(public a: string, private b: number) {}
}
class B {
constructor(public a: string) {}
}
const a = new A('', 12);
(a as B).a;
类型守卫
内置的类型守卫
- typeof
- instanceof
- in
- == , === , != ,!==
function fn(o: string | { b: string } | { a: number }) {
if (typeof o === 'string') {
o.charAt(0); // string类型
} else if ('a' in o) {
o.a; // {a:number}类型
} else {
o.b; //{b:string}类型
}
}
自定义类型守卫
type A ={a:string}
function isA(val: any): val is A{
return val?.a !== undefined
// 返回true ,则val is A
}
function test(val: any) {
if(isA(val)){ // 如果isA(val)是true才会进下面的代码块
val.a
// 推断为A类型, isA()返回true所进入的代码块中,
// val都会被类型断言为A
}
}
Tip:
- 类型守卫会自动推到并缩小类型
- 自定义守卫是另一种形式的类型断言
泛型
概念
- 类似函数形参,使用时传入类型具体化
- 泛型约束extends, T extends number
- 泛型默认值 List<T = any> ,如果没写默认就是unkown
- keyof,获取一个类,接口或对象类型的属性key(包括原型上的方法),组成的【联合类型】
- typeof, 运行时,返回的js类型 ; 编译时,如type A =typeof '12' // 获取'12'的类型,string, 赋值给A, 那么A就是string
type OT = {
a : sting ;
b : number;
}
class A<T extends keyof OT , K extends object>{
t:T // T extends 'a' | 'b'
}
type S = keyof 'ad' // S = 'slice' | 'charAt' | ... //原型上的属性方法也算
type Atype = typeof {a : 1} // Atype = {a:number}
泛型接口
interface List<T = any>{
add(element:T):number
}
泛型类
class Clist<T> implements List<T> {
add(element:T){return 12}
}
const clist = new Clist<string>()
泛型函数
- 函数调用的编译检查,==外部获取泛型函数调用的返回值根据定义(如果有定义)的返回类型决定==,而不管函数内部的实际返回
- 函数内部的编译检查,是假设泛型T一开始就是某种类型,如果函数的返回值类型为T,那么函数内部的所有返回类型智能是T或any , any[]也不行,但any[]可以赋值给T[]
function fn<T>(a: T): T[] {
return a as any; // 函数内部的as any ,有时可用于解决一些编译错误
}
const a =fn(12)
// a被判定是number[],但实际运行返回12
a.concat([12])
函数方法重载
概念
- 重载标签,调用时,调用的是重载标签的类型,然后进入函数体
- 实现标签,要兼容所有重载标签的类型
- 函数体,==函数体里的参数变量取的是实现标签的形参及默认值==
注意点
- 实现标签的参数只能比重载标签的参数少或者一样,==参数及其默认值是从实现标签里取的,重载标签里写参数默认值没有意义,而且重载标签里的参数名可以任意取==
普通
class List{
arr:object[]
// 方法
get(index:number):object
get(element:object):number
get(param:number|object):any{
if(typeof param === 'number'){
return this.arr[param]
}else{
return this.arr.indexOf(param)
}
}
}
// 函数类似
类型定义
// 接口定义的函数重载
interface Fn{
(index:number):object
(element:object):number
}
function fn(index:number):object;
function fn(element:object):number;
function fn(param:any = 12 ):any{
if(typeof param === 'number'){
return {}
}else {
return 12
}
}
const xx: Fn = fn
const a = xx(12) // a是object类型
const b = xx({}) // a是number类型
泛型函数重载
// 其他两种定义方式
type Fn2<T> = {
(index: number): number;
(element: T): T[];
};
interface Fn1 {
(index: number): number;
<T>(element: T): T[];
}
//
interface Fn<T> {
(index: number): number;
(element: T): T[];
}
function fn(index: number): number;
function fn<T>(element: T): T[];
function fn(param: any): any {
if (typeof param === 'number') {
return param;
} else {
return [param];
}
}
const xx: Fn<string> = fn;
// 函数调用后的返回类型,并不是根据函数体内的返回类型决定的,而是重载标签的返回类型决定的,类似类型断言
const a = xx(12); // a是number类型
const b = xx('123'); // a是string[]类型
构造器函数类型
概念
- 类变量自己的类型,typeof Aclass叫构造函数类型
- ts定义通用的构造器函数类型 type ConstructorType = {new (...args:any[]) : any }, 或者interface ConstructorType{ new (...args:any[]) : any }
- typeof Aclass 是ConstructorType子类
泛型构造器函数类型
type ConstructorType<T> = new (...args:any[]) => T
type ConstructorType<T> = { new (...args:any[]) : T } // 接口方式
工厂函数
type ConstructorType<T> = new (...args:any[]) => T
function createInstance<T>(tarClass: ConstructorType<T>):T{
return new tarClass()
}
// 调用
class A{
a = 12
}
const ins = createInstance(A)
交叉类型
概念
- 将多个类型合并(属性方法的==并集==,而不是交集)
- T & U是T & U & V 的子集,可以相互断言
- 合并过程是,遍历所有的key,每个相同的key进行类型合并,取类型交集,取不到就是never类型
// 例子1,同一个key,如果相同类型的,可选属性y会被非可选属性y覆盖
type X = {y?:number,z:string} & {y:number,x:string}
// {z:string,y:number,x:string}
// 例子2,同一个key,不同类型的,交叉并集后变成never类型,这样会没法赋值
type X = {y:string, z:string} & {y:number,x:string} // {y:never,z:string,x:string}
// 例子3,同一个key,不同类型的(但是一个是另一个的子类型的,子类型的可以赋值)
type X = {y: 1, z:string} & {y:number,x:string} // {y:1,z:string,x:string}
// y, 值类型1是number子类型,y可以赋值成1
联合类型
概念
- 或运算逻辑|
- ==对于赋值,对象的联合类型,可以接受联合类型中任意一种数据类型全部属性和方法,也可以是两个类型的全部属性和全部方法【可选属性和方法除外】,也可以是一种数据类型全部属性和方法+其他类型的某个属性和某个方法==
// 相当于取一个类型为子集,然后其他类型依次处理的所有key变为可选属性(同key则忽略)
type X = {a:number,d:number} | {a:string,b:number}
// {a:number,d:number,b?:number} 或 {a:string,b:number,d?number}
// 例子
type X = { a: number; d: number } | { a: string; b: number } | { b: string; x: number };
const x: X = { a: '',, d: 1, b: 12, x: 1 }; // {a,d}为一子集,其他皆各个类型的属性,
infer
概念
- 占位符式修饰数据类型的关键字,被修饰的数据类型要等到使用时才被推断出来(三元表达式)
三个场景
- extends条件语句后的函数类型的参数位置
- extends条件语句的返回值位置
- 类型泛型具体化的类型上
// 场景1
class Customer{}
type CustFunc = (cust:Customer)=>string
type inferType<T> = T extends (param: infer P) => any ? P : T
type X = inferType<CustFunc> // p = Customer ,如果不符合,则返回T类型
const x: X = new Customer()
// 场景2
type inferType2<T> = T extends (param: any)=>infer P ? P : T
type X2 = inferType2<CustFunc> // p = string类型
const x2: X2 = ''
// 场景3
type inferType3<T> = T extends Set<infer P> ? P : never
type X3 = inferType3<Set<string>> // P = string
const x3:X3 = ''
infer获取函数参数类型
(通过构造器函数类型匹配三元表达式条件)
type ConstructorParamterType<T extends new(...args:any[]) => any> =
T extends new(...args : infer P) => any ? P : never
type Constructor<T> = new (...args:any[])=>T
function createInstance<T , CP extends new (...args:any[])=>any>(targetClass: Constructor<T>, ...args:ConstructorParamterType<CP>){
return new targetClass(...args)
}
// 调用
class Test{
constructor(public age:number,readonly name:string){}
}
const ins=createInstance<Test, typeof Test>(Test,12,'名字')
//第一个Test是类型,第二个Test是类变量
高级类型
Extract(提取)
- 定义 type Extract<T,U>= T extends U ? T : never
- extends和类型断言的区别,(1)子类extends父类,为true (2)父类extends子类,一般为false,如果子类和父类的属性一样,为true
// 联合类型
type X1 = Extract< string|number|symbol, string|number > // string|number
// 前者会拿所有的类型一一跟后者的(所有)类型比较是否extends, 如果extends成立,则添加前者的该类型,不成立返回never,则忽略
// 函数
// 判断:函数的返回值的类型必须一致,参数少的extends参数多的
// 例子
type Func1 = (one:number, two:string) => string
type Func2 = (one:number) => string
type X2= Func1 extends Func2 ? Func1 : never // never
type X3 = Extract<Func1, Func2> // never
type X4 = Extract<Func2,Func1> // Func2
// 对象和接口,属性多的extends属性少的,鸭子辨形法,前者符合后者的所有属性即可
type X5 = Extract<{a:string}, {a:string;b:number}> // extends false , never
type X6 = Extract<{a:string; b:number}, {a:string}> // true , {a:string; b:number}
// 一个实际应用的技巧
// T extends object 可改写为类型Extract<T,object> ,如果不是object类型,会返回never,那肯定赋值错误
Exclude (排除)
- 定义 type Exclude<T, U> = T extends U ? never : T ,和Extract相反
// 联合类型
type X1 = Exclude< string|number|symbol, string|number > // symbol
// 函数,略
// 对象接口
type X5 = Exclude<{a:string}, {a:string;b:number}> // extends false , {a:string}
type X6 = Exclude<{a:string; b:number}, {a:string}> // true , never
Record<K,V>
- 定义
- P[K]
- ts规定, typeof any 等价于 string|number|symbol
- K in keyof T
- K in T
// Record
type Record<K extends any , T> = {
[P in K]: T
}
// 1. P[K]
type M = {a:string}
type A = M['a'] // string
// 2.
let x: Record<number,number> = [1] //数组的索引是数字
// 3. keyof any
2 extends keyof any
// 2 extends keyof string|number|symbol 成立
// 2 extends string
// 4. K in keyof T
keyof {a,b,c} // 'a'|'b'|'c'
// 5. K in T
[P in string] 表示任意一个字符串
// 6. object 和 Map
let o:object ={} ;
o.a //报错没有该属性
o['a'] //不报错
Pick(选取)
type Pick<T , K extends keyof T> = {
[P in K] : T[p]
}
type O ={a:string;b:number;c:boolean}
type PO= Pick<O, 'a'|'b'> // {a:string;b:number}
Partial(变可选)
type Partial<T> = {
[P in keyof T]? : T[P]
}
Required(可选变要求有,和Partial相反)
type Required<T> = {
[P in keyof T]-? : T[P]
// 如果没有-?,则无任何改变,可选属性依旧是可选属性
}
ReadOnly (只读)
type ReadOnly<T> = {
readonly[P in keyof T]: T[p]
}
Omit(省略)(与pick相反,排出)
type Omit<T, K extends keyof T>
= Pick< T, exclude<keyof T , K> >
类型声明文件
涉及语法
- declare var/const/let 声明全局变量
- declare function
- declare class
- declare enum
- declare namspace 声明(含有子属性)的全局对象
- interface 和 type 声明全局类型
- export 导出变量
- export namespace // 导出(含有子属性)的全局变量
- export default // ES6默认导出
- export = // commonjs 导出模块
- declare global 扩展全局变量
- declare module 扩展模块
- /// 三斜线指令
不同场景
1. 全局变量
场景:通过script标签引入的第三方库,注入全局变量 处理:在项目目录中新建global.d.ts ,或者 Jquery.d.ts等,只要能被ts编译 注意:
- declare namspace里头的无需在declare
- interface ,type无需declare
- interface定义在namespace里头避免全局污染,命名冲突
- 声明合并
- 三斜线指令,引入其他文件定义的类型(因为是全局声明,而非模块声明,不能用import ,只能用三斜线指令)
- 只定义类型,不能实现
// Jquery.d.ts
declare function jQuery(selector:string): any
declare function jQuery(domReadyCallback: () => any): any
declare namespace jQuery {
interface AjaxSettings {}
function ajax(url: string, settings?: any): void;
// 嵌套namespace
namespace $ {
fn():any
}
}
// index.d.ts
/// <reference types="jquery" />
declare function fetch(options:jQuery.AjaxSettings):string
declare enum Status {
on,
off
}
declare var window:any
declare class Image{
src: string
getWidth():number
}
2. npm包
import foo from 'foo'
2.1.方式:
- @types/foo,写好声明文件,发布@types
- (推荐)和npm包绑定在一起,查找声明文件顺序:(1)package.json中的字段types或typings,(2)index.d.ts
- 专门指定一个目录types,用来管理声明文件, tsconfig.json需要配置,告诉tsu去哪里找模块 声明文件
// tsconfig.json
{
"compileOptions":{
paths:{
"*":["types/*"]
}
}
}
- xx.js和xx.d.ts共存的情况下,vscode找寻模块会优先找d.ts
- declare module 'foo' {} // 全局声明模块,声明合并已有的模块声明
2.2 导出
- export 导出变量
- export namespace 导出对象
- export default // ES6默认导出
- export = // commonjs导出模块,相当于module.exports = xx
export 在npm包的声明文件中,declare不再会声明一个全局变量,只有导出的变量,才能被import引入
// 方式1
export const name:string
export class A {}
export interface X {}
// 方式2
declare const name:string
declare class A {}
declare interface X {}
export {name, A, X}
// 使用
import {name,A,X} from 'foo'
export namespace 导出一个拥有子属性的对象
export namespace foo{
function fn():void
}
// 使用
import {foo} from 'foo'
foo.fn()
export default 默认导出: 只有function ,class ,interface可以默认导出,其他变量需要闲定义出来,再默认导出
// 声明
declare const name:string
export { name }
export default function foo():string
import foo , {name} from 'foo'
foo()
export = 类似module.exports = ;用了export=后,就不能再单个导出了export {}
export = foo
declare function foo():string
import foo from 'foo'
const foo = require('foo')
export as namespace 用与umd库,支持导出为全局变量那种;一般是先有了包声明文件,再基于它添加一条export as namespace xx
export as namespace foo
export = foo
export default foo
declare function foo():string
declare namespace foo{
}
扩展全局变量
interface Window{
say():void
}
// 扩展已有的明名空间
declare namespace JQuery {
xx():void
}
npm包货umd库中扩展全局变量
declare global {
interface Window{
say():void
}
}
export {}
模块声明declare module
场景:一个模块扩展了一个模块插件,但类型声明文件只有原来模块部分,没有模块插件部分,这时候可以用declare module 扩展原来模块,补充模块插件的类型声明
声明的模块内部,要将导出的内容export才行
// types/moment-plugin/index.d.ts
import * as moment form 'moment'
declare module 'moment' {
export pluginFn(): moment.CalendatKey // 模块需要export
export interface xx {}
}
// 使用
import * as moment from 'moment'
import 'moment-plugin'
moment.pluginFn()
自动生成类型声明文件
tsconfig.json 里 compilerOptions.declaration =true; 会每个文件模块生成npm类型声明文件(去掉实现)
声明合并
- 接口,同属性不同类型会报错
- 方法,合并后类似重载
interface Alarm {
price: number;
alert(s: string): string;
}
interface Alarm {
weight: number;
alert(s: string, n: number): string;
}
//相当于
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}
// 如果一个price是string,另一个是number ,那么会报错