TypeScript 是什么
TS 是 JS 的超集,所以 JS 基础的类型都包含在内
TypeScript 开发环境搭建
全局安装 typescript:
1 npm install typescript -g
查看 typescript 当前版本号:
将 TS 编译成 JS (TS 不能直接在浏览器执行,需要编译器将 TS 转为 JS)
基础数据类型
基础类型:Number、String、Boolean、null、undefined 以及 ES6 的 Symbol 和 ES10 的 BigInt 。
字符串类型 1 2 3 4 5 6 7 8 let str : string = "这是字符串类型" ;let str1 : string = 666 ;let muban : string = `web${str} ` ;
数字类型 支持十六进制、十进制、八进制和二进制
1 2 3 4 5 6 7 let num : number = 123 ;let notANumber : number = NaN ;let infinityNumber : number = Infinity ;let decimal : number = 6 ;let hex : number = 0xf00d ;let binary : number = 0b1010 ;let octal : number = 0o744 ;
布尔类型 1 2 3 let b :boolean = false ;let b2 :boolean = Boolean (1 ); console .log (`b:${b} ,b2:${b2} ` );
空值类型
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void
表示没有任何返回值的函数
1 2 3 4 5 6 7 8 9 10 function fnVoid ( ): void { console .log ("test void" ); return ; } function fnVoid2 ( ): void { return 123 ; }
void
类型的用法,主要是用在我们不希望 调用者关心函数返回值的情况下,比如通常的异步回调函数
void 也可以定义 undefined 和 null 类型
1 2 let u : void = undefined let n : void = null ;
TIPS:
如果你配置了 tsconfig.json 开启了严格模式:则 null 不能 赋予 void 类型
1 2 3 4 5 { "compilerOptions" :{ "strict" : true } }
解决方法:
tsc --init
创建 tsconfig 文件;
tsconfig.json
文件内搜索 strict
,值改为 false
;
Null 和 undefined 类型 1 2 let u : undefined = undefined ;let n : null = null ;
void 和 undefined 和 null 最大的区别
void 的内容也是不能去赋值给别人的
1 2 3 4 5 6 let u : void = undefined ;let n : null = null ;let str : string = "123" ;str = u;
下面这样 undefined 与 null 可以赋值
1 2 3 4 5 let u : undefined = undefined ;let n : null = null ;let str : string = "123" ;str = u;
NodeJs 环境执行 TS
上面每次执行代码,都需要将 TS 编译成 JS,然后再通过 NodeJs 来执行 JS,有点繁琐。
NodeJs 直接运行 TS 需要安装两个依赖:
npm install @types/node -D
(node 环境支持的依赖必装)
npm install ts-node -g
输出版本号:ts-node -v
NodeJs 执行 TS:ts-node xxx.ts
Any 类型 和 unknown 顶级类型
any 与 unknown 没有强制限定哪种类型,可以定义任何类型的值,被称为顶级类型 (top type)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let anys : any ;anys = true ; anys = 18 ; anys = "Hello World" ; anys = []; anys = {}; anys = null ; anys = undefined ; anys = Symbol ("type" ); let value : unknown ;value = true ; value = 18 ; value = "Hello World" ; value = []; value = {}; value = null ; value = undefined ; value = Symbol ("type" );
any 与 unknow 区别
TypeScript 3.0 中引入的 unknown 类型也被认为是 top type ,但它更安全。与 any 一样,所有类型都可以分配给 unknown
unknow 类型比 any 更加严格当你要使用 any 的时候可以尝试使用 unknow
unknown 可赋值对象只有 unknown 和 any
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let names : unknown = '123' ;let names2 : string = names; let ages : any = 123 ;let ages2 : string = ages;let aaa : unknown = '123' ;let bbb : unknown = '456' ;let ccc : any = "789" ;aaa = bbb; aaa = ccc;
unknow 不能调用属性和方法
1 2 3 4 5 6 7 8 9 let obj : any = { b : 1 };obj.a ; let obj2 : unknown = { b : 1 , ccc : (): number => 213 };obj2.b ; obj2.ccc ();
声明变量的时候没有指定任意类型默认为 any (如果使用 any 就失去了 TS 类型检测的作用)
接口和对象类型 interface 定义对象类型
在 JS 中,我们声明对象,直接写就好了
1 2 3 4 let obj = { name : "fsllala" , age : 18 }
在 TS 中,我们定义对象的方式要用关键字 interface (接口),我的理解是使用 interface 来定义一种约束 (类型),让数据的结构满足约束的格式。定义方式如下:
使用接口约束的时候不能多一个属性也不能少一个属性,必须与接口保持一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Person { name :string , age :number } const person :Person ={ name :"fsllala" }
重名 interface 可以合并
1 2 3 4 5 6 7 8 9 10 11 12 13 interface A { name : string } interface A { age : number } let obj : A = { name : "fsllala" , age : 18 }; console .log (obj);
可选属性 使用?操作符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Person { name : string , age?: number } const person : Person = { name : "fsllala" } const person2 : Person = { name : "fsl" , age : 18 }
索引签名 [propName: string]
后端给我们返回的字段不确定,类型也不确定的时候可以用任意属性;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Person { name : string , age : number , [propName : string ]: any } const person : Person = { name : "fsl" , age : 18 , ccc : 123 , ddd :'456' , }
只读属性 readonly
readonly 只读属性是不允许被赋值的只能读取;(跟 input 的 readonly 属性类似);
常用于函数
和后台返回的数据 ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 interface Person { readonly id : number name : string , age : number , [propName : string ]: any , readonly cb : () => boolean } const person : Person = { id : 2 , name : "fsl" , age : 18 , ccc : 123 , cb : () => { return false } } person.id = 3 ;
添加函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 interface Person { name : string , readonly age : number , [propName : string ]: any , cb (): number , foo : () => boolean } const person : Person = { name : "fsl" , age : 18 , ccc : 123 , dd : 111 , cb : () => { return 456 }, foo : () => { return false } }
继承
和 ES6 中类的继承类似;
1 2 3 4 5 6 7 8 9 10 11 12 interface A { name : string } interface B extends A { age : number } let obj : B = { name : "fsllala" , age : 18 };
interface 定义函数类型 1 2 3 4 5 6 7 8 9 10 interface Fn { (name : string ): number [] } const foo : Fn = function (name: string ) { return [1 , 2 , 3 , 4 ] }
数组类型
数组类型有多种声明方法:
普通方式:类型 [ ]
1 2 3 4 let arr : number [] = [1 , 2 , 3 ]; let arr2 : string [] = ["1" , "2" , "3" ]; let arr3 : boolean [] = [true , false ];let arr4 : any [] = [1 , "2" , true ,[],{}];
数组泛型 :Array <类型>
1 2 3 4 let arr : Array <number > = [1 , 2 , 3 ]; let arr2 : Array <string > = ["1" , "2" , "3" ]; let arr3 : Array <boolean > = [true , false ];let arr4 : Array <any > = [1 , "2" , true , [], {}];
用接口表示数组:一般用来描述类数组
1 2 3 4 5 6 interface NumArray { [index : number ]: number } let arr : NumArray = [1 , 2 , 3 ];
对象数组
使用 interface
方式来定义对象数组
1 2 3 4 5 6 interface X { name : string , age?: number } const arr1 : X[] = [{ name : "fsllala" , age : 18 }, { name : "fsl" }];
多维数组 1 2 3 4 5 6 let arr : number [][] = [[1 , 2 ], [3 , 4 ]]; let arr2 : number [][][] = [[[1 ]], [[2 ]]];let arr3 : Array <Array <number >> = [[1 , 2 ], [3 , 4 ]]; let arr4 : Array <Array <Array <number |string >>> = [[[1 ]], [[2 ,"lala" ]]];
arguments 类数组
函数内置的 arguments,是个类数组,其实就是有 length 属性的对象;所以没有数组的方法:push、pop…
1 2 3 4 5 6 function Arr (...args: any ): void { console .log (arguments ); console .log (args); } Arr (2 , 3 , 4 );
所以就会导致如下情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Arr (...args: any ): void { let arr : number [] = arguments ; let arr2 : number [] = args; let arr3 :IArguments =arguments ; } Arr (2 , 3 , 4 ); interface IArguments {[index : number ]: any ; length : number ;callee : Function ;}
函数拓展
匿名函数可以进行类型推导,不推荐在写类型,因为可能会写错;
1 2 3 4 5 6 const arr1 = ["lala" , "wawa" , "miaomiao" ];arr1.forEach ((item, index ) => { console .log (item, index); })
interface 定义函数类型;
1 2 3 4 5 6 7 8 9 interface Ikun { (name : string ): string } const kunkun : Ikun = (aa : string ): string => { return aa; } kunkun ("篮球" );
函数的调用签名 (从对象的角度看待这个函数,也可以有其他属性);
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IBar { names : string , age : number , (num : number ): number } const bar2 : IBar = (arg : number ): number => { return arg; } bar2.names = "fsllala" ; bar2.age = 18 ; console .log (bar2 (222 ))
type 定义函数类型;
1 2 3 4 5 6 7 8 type Bar = (num1: number ) => number ;const bar : Bar = (arg : number ): number => { return arg; } console .log (bar (111 ));
参数不能多传,也不能少传 必须按照约定的类型来
1 2 3 4 5 6 7 const fn = function (name: string , age: number ): string { return name + age; } let result = fn ("fsllala" , 18 );console .log (result);
少传参数:定义默认的参数
1 2 3 4 5 6 7 const fn = function (name: string , age: number = 24 ): string { return name + age; } let result = fn ("fsllala" );console .log (result);
可选属性 使用?操作符 (不传默认为 undefined)
1 2 3 4 5 6 7 const fn = function (name: string , age?: number ): string { return name + age; } let result = fn ("fsllala" );console .log (result);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface User { name : string , age : number } const fn = function (user: User ): User { return user; } let result = fn ({ name : "fsllala" , age : 18 }) console .log (result);
函数重载:方法名字相同,而参数不同,与返回值无关。
如果参数类型不同,则参数类型应设置为 any 。
参数数量不同你可以将不同的参数设置为可选。
1 2 3 4 5 6 7 8 9 10 function fn (params: number ): void function fn (params: string , params2: number ): void function fn (params: any , params2?: any ): void { console .log (params) console .log (params2) } fn (123 )fn ('123' ,456 )
Tip:JS 好像是不能这样写的,JS 重载是通过函数内部的 arguments 的长度来实现的;
类型断言 | 联合类型 | 交叉类型 联合类型
可以是多种类型,中间用 |
隔开
1 2 3 4 5 6 7 let myPhone : number | string = '010-820' let myPhone2 : number | string = true
函数使用联合类型
1 2 3 4 5 6 7 8 const fn1 = (types :number | boolean ):boolean => { return !!types } const result =fn1 (1 );const result2 = fn1 (false );console .log (result); console .log (result2);
交叉类型
类似于 extends
多种类型的集合,联合对象将具有所联合类型的所有成员,中间用 &
隔开;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface People { age : number , height : number } interface Man { sex : string } const fsllala = (man: People & Man ) => { console .log (man.age ) console .log (man.height ) console .log (man.sex ) } fsllala ({ age : 18 , height : 173 , sex : 'male' });
类型断言
在使用联合类型
的时候,可能会遇到某个属性 A 类型没有,B 类型有,比如 length
属性 number没有
,string有
;
1 2 3 4 5 let fn1 = function (params:number |string ):void { console .log (params.length ); } fn1 ("123456" );
这个时候就得用到类型断言
:需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
1 值 as 类型 或 <类型>值 value as string <string >value
1 2 3 4 5 6 let fn1 = function (params: number | string ): void { console .log ((params as string ).length ); } fn1 ("123456" ); fn1 (123456 );
使用 any 临时断言
1 2 (window as any ).abc = 123
内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
ECMAScript 的内置对象 Boolean、Number、string、RegExp、Date、Error
1 2 3 4 5 6 7 8 9 10 11 12 let b : Boolean = new Boolean (1 )console .log (b)let n : Number = new Number (true )console .log (n)let s : String = new String ('lala~' )console .log (s)let d : Date = new Date ()console .log (d)let r : RegExp = /^1/ console .log (r)let e : Error = new Error ("error!" )console .log (e)
DOM 和 BOM 的内置对象 Document
、HTMLElement
、Event
、NodeList
等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 let body : HTMLElement = document .body ;let allDiv : NodeList = document .querySelectorAll ('div' );let div :HTMLElement = document .querySelector ('div' ) as HTMLDivElement document .addEventListener ('click' , function (e: MouseEvent ) { }); interface HTMLElementTagNameMap { "a" : HTMLAnchorElement ; "abbr" : HTMLElement ; "address" : HTMLElement ; "applet" : HTMLAppletElement ; "area" : HTMLAreaElement ; "article" : HTMLElement ; "aside" : HTMLElement ; "audio" : HTMLAudioElement ; "b" : HTMLElement ; "base" : HTMLBaseElement ; "bdi" : HTMLElement ; "bdo" : HTMLElement ; "blockquote" : HTMLQuoteElement ; "body" : HTMLBodyElement ; "br" : HTMLBRElement ; "button" : HTMLButtonElement ; "canvas" : HTMLCanvasElement ; "caption" : HTMLTableCaptionElement ; "cite" : HTMLElement ; "code" : HTMLElement ; "col" : HTMLTableColElement ; "colgroup" : HTMLTableColElement ; "data" : HTMLDataElement ; "datalist" : HTMLDataListElement ; "dd" : HTMLElement ; "del" : HTMLModElement ; "details" : HTMLDetailsElement ; "dfn" : HTMLElement ; "dialog" : HTMLDialogElement ; "dir" : HTMLDirectoryElement ; "div" : HTMLDivElement ; "dl" : HTMLDListElement ; "dt" : HTMLElement ; "em" : HTMLElement ; "embed" : HTMLEmbedElement ; "fieldset" : HTMLFieldSetElement ; "figcaption" : HTMLElement ; "figure" : HTMLElement ; "font" : HTMLFontElement ; "footer" : HTMLElement ; "form" : HTMLFormElement ; "frame" : HTMLFrameElement ; "frameset" : HTMLFrameSetElement ; "h1" : HTMLHeadingElement ; "h2" : HTMLHeadingElement ; "h3" : HTMLHeadingElement ; "h4" : HTMLHeadingElement ; "h5" : HTMLHeadingElement ; "h6" : HTMLHeadingElement ; "head" : HTMLHeadElement ; "header" : HTMLElement ; "hgroup" : HTMLElement ; "hr" : HTMLHRElement ; "html" : HTMLHtmlElement ; "i" : HTMLElement ; "iframe" : HTMLIFrameElement ; "img" : HTMLImageElement ; "input" : HTMLInputElement ; "ins" : HTMLModElement ; "kbd" : HTMLElement ; "label" : HTMLLabelElement ; "legend" : HTMLLegendElement ; "li" : HTMLLIElement ; "link" : HTMLLinkElement ; "main" : HTMLElement ; "map" : HTMLMapElement ; "mark" : HTMLElement ; "marquee" : HTMLMarqueeElement ; "menu" : HTMLMenuElement ; "meta" : HTMLMetaElement ; "meter" : HTMLMeterElement ; "nav" : HTMLElement ; "noscript" : HTMLElement ; "object" : HTMLObjectElement ; "ol" : HTMLOListElement ; "optgroup" : HTMLOptGroupElement ; "option" : HTMLOptionElement ; "output" : HTMLOutputElement ; "p" : HTMLParagraphElement ; "param" : HTMLParamElement ; "picture" : HTMLPictureElement ; "pre" : HTMLPreElement ; "progress" : HTMLProgressElement ; "q" : HTMLQuoteElement ; "rp" : HTMLElement ; "rt" : HTMLElement ; "ruby" : HTMLElement ; "s" : HTMLElement ; "samp" : HTMLElement ; "script" : HTMLScriptElement ; "section" : HTMLElement ; "select" : HTMLSelectElement ; "slot" : HTMLSlotElement ; "small" : HTMLElement ; "source" : HTMLSourceElement ; "span" : HTMLSpanElement ; "strong" : HTMLElement ; "style" : HTMLStyleElement ; "sub" : HTMLElement ; "summary" : HTMLElement ; "sup" : HTMLElement ; "table" : HTMLTableElement ; "tbody" : HTMLTableSectionElement ; "td" : HTMLTableDataCellElement ; "template" : HTMLTemplateElement ; "textarea" : HTMLTextAreaElement ; "tfoot" : HTMLTableSectionElement ; "th" : HTMLTableHeaderCellElement ; "thead" : HTMLTableSectionElement ; "time" : HTMLTimeElement ; "title" : HTMLTitleElement ; "tr" : HTMLTableRowElement ; "track" : HTMLTrackElement ; "u" : HTMLElement ; "ul" : HTMLUListElement ; "var" : HTMLElement ; "video" : HTMLVideoElement ; "wbr" : HTMLElement ; }
定义 Promise 如果我们不指定返回的类型 TS 是推断不出来返回的是什么类型
1 2 3 4 5 function promise ( ){ return new Promise ((resolve, reject ) => { resolve (1 ) }) }
指定返回的类型
1 2 3 4 5 function promise ( ){ return new Promise <number >((resolve, reject ) => { resolve (1 ) }) }
函数定义返回 promise 语法规则:Promise<T>
1 2 3 4 5 6 7 8 9 function promise ( ): Promise <number > { return new Promise <number >((resolve, reject ) => { resolve (1 ) }) } promise ().then (res => { console .log (res); })
Class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。详见:面向对象
定义 在 TypeScript 是不允许直接在 constructor 定义变量的
1 2 3 4 5 6 7 8 9 10 class Person { constructor (name: string , age: number , sub: boolean ) { this .name11 = name; this .age11 = age; this .sub11 = sub; } } const person = new Person ("fsllala" , 18 , false );
需要在 constructor 上面先声明
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { name11 :string age11 :number sub11 :boolean constructor (name: string , age: number , sub: boolean ) { this .name11 = name; this .age11 = age; this .sub11 = sub; } } const person = new Person ("fsllala" , 18 , false );
类的修饰符
总共有三个:public private protected
public (class 默认):class 定义的属性或方法内部外部都能访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { public name11 : string age11 : number sub11 : boolean constructor (name: string , age: number , sub: boolean ) { this .name11 = name; this .age11 = age; this .sub11 = sub; } } const person = new Person ("fsllala" , 18 , false );console .log (person.name11 ); console .log (person.age11 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { public name11 : string private age11 : number sub11 : boolean constructor (name: string , age: number , sub: boolean ) { this .name11 = name; this .age11 = age; this .sub11 = sub; } } const person = new Person ("fsllala" , 18 , false );console .log (person.name11 ); console .log (person.age11 );
protected:只能在内部和继承的子类中访问 不能在外部访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person { public name11 : string private age11 : number protected sub11 : boolean constructor (name: string , age: number , sub: boolean ) { this .name11 = name; this .age11 = age; this .sub11 = sub; } } class Man extends Person { constructor ( ){ super ("fsllala" , 18 , false ); console .log (this .name11 ); console .log (this .age11 ); console .log (this .sub11 ); } }
static 静态属性 和 静态方法
静态方法中的 this 指向类本身,非静态方法的 this 指向实例对象;
即:静态方法中的 this,只能调用 static
定义的属性或方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person { public name11 : string private age11 : number protected sub11 : boolean static aaa :string ="静态属性直接赋值" ; constructor (name: string , age: number , sub: boolean ) { this .name11 = name; this .age11 = age; this .sub11 = sub; } static run ():string { this .aaa ; return "静态方法中的this指向对象本身" } } console .log (Person .aaa );console .log (Person .run ());
interface 定义类
TS interface 定义类,使用关键字 implements ;后面跟 interface 的名字,多个用逗号隔开;继承还是用 extends;
与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。
extends 可以接口继承接口,可以类继承类;implements 是类实现接口;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 interface PersonClass { get (type : boolean ): boolean } interface PersonClass2 { set (): void , asd : string } class A { name : string constructor ( ) { this .name = "123" } } class Person extends A implements PersonClass , PersonClass2 { asd : string constructor ( ) { super () this .asd = '123' } get (type : boolean ) { return type } set ( ) { } }
抽象类
应用场景:如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
或者你也可以把他作为一个基类 -> 通过继承一个派生类去实现基类的一些方法
1 2 3 4 5 6 abstract class A { public name :string } new A ()
我们在 A 类定义了 getName 抽象方法但未实现;
我们 B 类实现了 A 定义的抽象方法 如果实现就不报错 我们定义的抽象方法必须在派生类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 abstract class A { name : string constructor (name: string ) { this .name = name; } setName (name: string ) { this .name = name; } abstract getName (): string } class B extends A { constructor ( ) { super ('lala' ) } getName (): string { return this .name } } let b = new B ();b.setName ("haha~" ) console .log (b.getName ());
元组类型
元组就是数组的变种;
如果需要一个固定大小的不同类型值的集合,我们需要使用元组;
元组(Tuple)是固定数量的不同类型的元素的组合。
元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。
1 2 3 let arr : [number , string ] = [1 , 'string' ]let arr2 : readonly [number , boolean , string , undefined ] = [1 , true , 'sring' , undefined ]
当赋值或访问一个已知索引的元素时,会得到正确的类型:
1 2 3 4 5 let arr :[number ,string ] = [1 ,'string' ]arr[0 ].length arr[1 ].length
越界元素
1 2 3 4 let arr :[number ,string ] = [1 ,'string' ];arr.push (boolean ); arr.push (1 );
应用场景 例如定义 excel 返回的数据
1 2 3 4 5 6 7 let excel : [string , string , number , string ][] = [ ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ];
枚举类型
在 JavaScript 中是没有枚举的概念的 TS 帮我们定义了枚举这个类型;
使用枚举:通过 enum 关键字定义我们的枚举;
作用:定义常量,约束统一;
数字枚举 例如 红绿蓝 Red = 0 Green = 1 Blue= 2 分别代表红色 0 绿色为 1 蓝色为 2
1 2 3 4 5 6 7 8 9 enum Color { Red , Green , Blue , } console .log (Color .Red ); console .log (Color .Green ); console .log (Color .Blue );
这样写就可以实现应为 ts 定义的枚举中的每一个组员默认都是从 0 开始的所以也就是
1 2 3 4 5 6 7 8 9 10 enum Color { Red = 0 , Green = 1 , Blue = 2 , } console .log (Color .Red ); console .log (Color .Green ); console .log (Color .Blue );
增长枚举
1 2 3 4 5 6 7 8 9 enum Color { Red = 5 , Green , Blue , } console .log (Color .Red ); console .log (Color .Green ); console .log (Color .Blue );
自定义枚举
1 2 3 4 5 6 7 8 9 enum Color { Red = 5 , Green = 13 , Blue = 18 , } console .log (Color .Red ); console .log (Color .Green ); console .log (Color .Blue );
字符串枚举 1 2 3 4 5 6 7 8 9 enum Color { Red = 'red' , Green = 'green' , Blue = 'blue' } console .log (Color .Red ); console .log (Color .Green ); console .log (Color .Blue );
异构枚举 枚举可以混合字符串和数字成员
1 2 3 4 enum Color { No = "No" , Yes = 1 , }
接口枚举 定义一个枚举 Color 定义一个接口 AA 他有一个属性 red 值为 Color.Yes
声明对象的时候要遵循这个规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 enum Color { No = "No" , Yes = 1 , } interface AA { red : Color .Yes } let obj1 : AA = { red : 1 }
const 枚举
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const
枚举。 常量枚举通过在枚举上使用 const
修饰符来定义
let 和 var 都是不允许声明 enum 的,只能使用 const 声明
const 声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象
普通声明编译之后
1 2 3 4 5 6 7 8 9 enum Color { Yes , No , } let code : number = 0 ;if (code === Color .No ) {}
JS:
1 2 3 4 5 6 7 8 var Color ;(function (Color ) { Color [Color ["Yes" ] = 0 ] = "Yes" ; Color [Color ["No" ] = 1 ] = "No" ; })(Color || (Color = {})); var code = 0 ;if (code === Color .No ) {}
const 声明编译之后
1 2 3 4 5 6 7 8 9 const enum Color { Yes , No , } let code : number = 0 ;if (code === Color .No ) {}
JS:
1 2 3 var code = 0 ;if (code === 1 ) {}
反向映射
它包含了正向映射( name
-> value
)和反向映射( value
-> name
)
要注意的是 不会 为字符串枚举成员生成反向映射。
1 2 3 4 5 6 7 enum Enum { success } let result : number = Enum .success ;console .log (result); let result2 = Enum [result];console .log (result2);
类型推论 | 类型别名 类型推论
我声明了一个变量但是没有定义类型
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论
1 2 3 let str = "fsllala" ;str = 18 ;
如果你声明变量没有定义类型也没有赋值这时候 TS 会推断成 any 类型可以进行任何操作
1 2 3 4 5 let strstr = 123 str = "马杀鸡" str = false str = []
类型别名
type 关键字(可以给一个类型定义一个名字)多用于复合类型
1 2 3 4 type s = string ;let str : s = "fsllala" ;console .log (str);
1 2 3 type s = string |number let str :s = "永恒的紫罗兰花园" let num :s = 520
1 2 3 4 type s = () => string let str : s = () => "fsllala" ;console .log (str ());
1 2 3 4 5 type value = boolean | 0 | '213' let s : value = "213" ;let s2 : value = "111" ;
never 类型
TypeScript 将使用 never 类型来表示不应该存在的状态,也就是值永不存在的类型。
值会永不存在的两种情况:
如果一个函数执行时抛出了异常 ,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了);
数中执行无限循环的代码(死循环 ),使得程序永远无法运行到函数返回值那一步,永不存在返回。
1 2 3 4 5 6 7 8 9 10 11 12 function error (message: string ): never { throw new Error (message); } function loop ( ): never { while (true ) { } }
举一个我们可能会见到的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 interface Foo { type : "保安" } interface Bar { type : "草莓" } type All = Foo | Bar function handleValue (val: All ) { switch (val.type ) { case '保安' : break case '草莓' : break default : const exhaustiveCheck : never = val break } } handleValue ({ type : "草莓" });
比如新来了一个同事他新增了一个 C 接口,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 interface Foo { type : "保安" } interface Bar { type : "草莓" } interface C { type : "卷心菜" } type All = Foo | Bar | Cfunction handleValue (val: All ) { switch (val.type ) { case '保安' : break case '草莓' : break default : const exhaustiveCheck : never = val; break } } handleValue ({ type : "草莓" });
所以通过这个办法,你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。
symbol 类型
ES6 引入了一种新的基本数据类型 Symbol
,表示独一无二的值;
内存地址的指针位置不同,所以是唯一值;
1 2 3 4 5 6 7 8 9 10 11 let name1 : symbol = Symbol ("fsllala" );let name2 : symbol = Symbol ("fsllala" );let age :symbol =Symbol (18 );console .log (name1 === name2);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let name1 : symbol = Symbol ("fsllala" );let name2 : symbol = Symbol ("fsllala" );let age : symbol = Symbol (18 );let obj1 = { [name1]: "forward" , [age]: 24 , sex : 1 , height : 173 , } for (let i in obj1) { console .log (i); } console .log (Object .keys (obj1)); console .log (Object .getOwnPropertyNames (obj1));
静态方法 Reflect.ownKeys () 返回一个由目标对象自身的属性键组成的数组 (Array)。
语法:Reflect.ownKeys (target) => target 获取自身属性键的目标对象。
Reflect.ownKeys 方法返回一个由目标对象自身的属性键组成的数组 。它的返回值等同于 Object.getOwnPropertyNames (target).concat (Object.getOwnPropertySymbols (target) ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let name1 : symbol = Symbol ("fsllala" );let name2 : symbol = Symbol ("fsllala" );let age : symbol = Symbol (18 );let obj1 = { [name1]: "forward" , [age]: 24 , sex : 1 , height : 173 , } console .log (Object .getOwnPropertySymbols (obj1)); console .log (Reflect .ownKeys (obj1));
Symbol.iterator 迭代器 和 生成器 for of
一般来说 Object 对象是不能通过 for…of 来进行遍历的;
1 2 3 4 5 6 7 8 let obj1 = { name :"fsllala" , age :18 , sex :1 } for (let i of obj1);
可以给 Object 对象提供一个 Iterator 方法,使对象也能使用 for…of 遍历;
要知道 js 的迭代器,我们就要先知道迭代的意思,实际上迭代的本意是遍历,随着不断有新的数据类型加入,我们就要用不同的 for 语句去循环,这就显得很麻烦,所以就有了迭代的概念
所有数据类型的遍历都遵循的一个协议。所以以后你对遵循了迭代协议的数据类型 你只需要用迭代去遍历就可以了。顺便一提:for…of 是用迭代器来实现的,只要你能用 for…of 来遍历的数据都是实现了迭代协议的数据
string,array,map,set,arguments,Nodelist 等 dom 元素 ;注意注意;没有 object
for…of 会遍历可迭代的对象,调用对象上的 Symbol.iterator 方法。(此对象非彼对象,这个对 象是指你即将下手的目标)
对象也是不支持的,因为对象没用 Symbol.iterator 方法。
迭代器
迭代器 Iterator 的用法
Iterator 是 es6 引入的一种新的遍历机制 ,两个核心:
迭代器是一个统一的接口,它的作用是使各种 数据结构 可被便捷的访问,它是通过一个键为 Symbol.iterator 的方法来实现。
迭代器是用于 遍历 数据结构元素的指针(如数据库中的游标)。
使用迭代:
实现的迭代协议的数据类型,里面都会有一个 [Symbol.iterator] 方法
进行迭代的时候用迭代器的 next 来取值,这个 next 返回的是一个对象:{value:value,done:"是否完成遍历"}
当 value 是 underfined 、done 为 true 时则遍历结束
实现的迭代协议的数据类型:
string、array、map、set、arguments、Nodelist等dom元素;注意注意;没有object
1 2 3 4 5 6 7 8 9 const str : string = "fsl" ;const iterator : Iterator <string > = str[Symbol .iterator ]();console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ());
1 2 3 4 5 6 7 8 9 let arr : Array <number > = [1 , 5 , 6 ]; let iterator : Iterator <number > = arr[Symbol .iterator ]()console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ());
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let arr : Array <number | string > = ["泛型" , "实现简单的迭代器" ];let set : Set <number > = new Set ([1 , 2 , 3 , 4 ]); function iteratorFun (args: any ): void { const it : Iterator <any > = args[Symbol .iterator ](); let next : any = { done : false }; while (!next.done ) { next = it.next (); if (!next.done ) { console .log (next); } } } iteratorFun (arr);iteratorFun (set);
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候在指定类型的一种特性
函数泛型 我写了两个函数一个是数字类型的函数,另一个是字符串类型的函数,其实就是类型不同,实现的功能是一样的,这时候我们就可以使用泛型来优化。
1 2 3 4 5 6 7 8 9 10 function num (a: number , b: number ): Array <number > { return [a, b]; } function str (a: string , b: string ): Array <string > { return [a, b]; } console .log (num (1 , 2 )); console .log (str ('fsllala' , '18' ));
语法为函数名字后面跟一个 <参数名> 参数名可以随便写 例如我这儿写了 T;
当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型);
即:定义这个函数的时候,不定义参数的类型,而是让调用者告知我这里的参数是什么类型;
1 2 3 4 5 6 7 8 9 10 function add<T>(a : T, b : T): Array <T> { return [a, b]; } add<number >(1 ,2 ); add<string >("fsllala" ,'18' ); add (1 ,2 )add ('1' ,'2' )
我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
1 2 3 4 5 6 function sub<T, U>(a : T, b : U): Array <T | U> { let arr : Array <T | U> = [a, b]; return arr; } sub<number ,boolean >(66 ,true );
泛型接口
声明接口的时候 在名字后面加一个 <参数>
使用的时候传递类型
1 2 3 4 5 6 7 8 9 10 11 interface IKun <T> { name : T, age : number , hobby : T[] } const kunkun : IKun <string > = { name : "lala" , age : 18 , hobby : ["唱" , "跳" , "rap" , "篮球" ] }
1 2 3 4 5 6 7 8 9 10 11 12 interface IKun <T = string > { name : T, age : number , hobby : T[] } const kunkun : IKun = { name : "lala" , age : 18 , hobby : ["唱" , "跳" , "rap" , "篮球" ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface CreateArrayFunc <T> { (length : number , value : T): Array <T> } let createArrayFunc : CreateArrayFunc <string >;createArrayFunc = function <T>(length : number , value : T): Array <T> { let result : T[] = []; for (let i = 0 ; i < length; i++) { result[i] = value; } return result; } let results = createArrayFunc (3 , "x" );console .log (results);
泛型类 1 2 3 4 5 6 7 8 9 10 class Point <T>{ x : T y : T constructor (x: T, y: T ) { this .x = x; this .y = y; } } const point1 = new Point <number >(11 , 22 );const point2 = new Point <string >('11' , '22' );
泛型约束 1 2 3 4 function getLength<T>(args :T){ return args.length ; }
1 2 3 4 5 6 7 8 9 10 11 12 interface Ilength { length : number } function getLength<T extends Ilength >(args : T) { return args.length ; } getLength ("123" );getLength ([1 , 2 ]);getLength ({ length : 77 });
使用 keyof
约束对象
平时在开发中我们可能会看到一些常用的名称:
T:Type 的缩写,类型;
K、V:key 和 value 的缩写,键值对;
E:Element 的缩写,元素;
O:Object 的缩写,对象;
下面场景不会报错:
1 2 3 4 5 6 7 function prop<O>(obj : O, keys) { return obj[keys]; } let info = { a : 1 , b : 2 , c : 3 };prop (info, "a" );console .log (prop (info, "d" ));
解决方法:其中使用了 TS 泛型和泛型约束。使用 extends
关键字添加泛型约束,keyof 操作符获取对象键的联合类型;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface IKun { name : string , age : number } type IKunKeys = keyof IKun ; const kunkun : IKunKeys = "name" ;const kunkun2 : IKunKeys = "age" ;const kunkun3 : IKunKeys = "sex" ;
如上问题的解决:
1 2 3 4 5 6 7 8 function prop<O, K extends keyof O>(obj : O, keys : K) { return obj[keys]; } let info = { a : 1 , b : 2 , c : 3 };prop (info, "a" );console .log (prop (info, "d" ));
参考文献 TS 系列篇|泛型
模块化
先来看一个现象:不同 TS 模块下会产生命名冲突:
1 2 3 const name1 = "fsllala123" ;console .log (name1);
1 2 3 const name1="fsllala456" ;console .log (name1);
什么是模块化?
JavaScript 规范声明任何没有 export 的 JavaScript 文件都应该被认为是一个脚本,而非一个模块。在一个脚本文件中,变量和类型会被声明在共享的全局作用域。
如果你有一个文件,现在没有任何 import 或者 export,但是你希望它被作为模块处理,添加这行代码; 这会把文件改成一个没有导出任何内容的模块,这个语法可以生效,无论你的模块目标是什么。
ES Module
TypeScript 中最主要使用的模块化方案就是 ES Module,也就是 ES6 的写法,详见文章模块化规范 ;
TS 文件中,即使两个不同的 TS 文件,比如 index.ts
和 index2.ts
,命名同一个变量,也会起冲突。因为它的内容被视为全局可见的(因此对模块也是可见的)
TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块。相反地,如果一个文件不带有顶级的 import 或者 export 声明,那么它的内容被视为全局可见的(因此对模块也是可见的)
ES6 模块化
解决全局变量冲突 (每个文件添加 export {} 即可)
1 2 3 4 const name1 = "fsllala123" ;console .log (name1);export {}
1 2 3 4 const name1="fsllala456" ;console .log (name1); export {}
1 2 3 const name1 = "fsllala123" ;export { name1 }
1 2 3 import { name1 } from "./index" console .log (name1);
内置类型导入 1 2 3 4 5 6 7 8 export interface IKun { name : string , age : number , hobby : string [] } export type IPerson = string | number ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import type { IKun , IPerson } from "./type" ; const person1 : IPerson = "fsllala" ;const kunkun : IKun = { name : "xiaoheizi" , age : 18 , hobby : ["唱" , "跳" , "rap" , "篮球" ] } console .log (kunkun);
这里需要注意的是:不使用 type 前缀,也可以导入类型。那么为啥要加上 type 前缀呢?
TS 最终要被编译成 JS 代码,类型是没用的,通过语法识别 TS/JS 的成本要比直接引入的时候就用 type 表明了是类型的成本大,到时候类型部分的代码就直接删除了;
namespace 命名空间
namespace 常规写法
1 2 3 4 5 6 7 namespace AA { const name1 = "fsllala" ; const age1 = 18 ; } console .log (AA );
1 2 3 4 5 6 namespace AA { export const name1 = "fsllala" ; export const age1 = 18 ; } console .log (AA );
同一模块 (不同模块) 下有重名的 namespace,里面属性会合并
因为属性会合并,所以会出现以下情况:
1 2 3 4 5 6 7 8 9 10 namespace AA { export const name1 = "fsllala" ; } namespace AA { export const age1 = 18 ; } console .log (AA );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 namespace AA { export const name1 = "fsllala" ; export const age1 = 18 ; } namespace AA { export const name1 = "fsllala" ; export const age1 = 18 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 namespace AA { export var name1 = "fsllala" ; export var age1 = 18 ; } namespace AA { export var name1 = "fsllala" ; export var age1 = 18 ; } console .log (AA );
嵌套命名空间 1 2 3 4 5 6 7 8 9 namespace AA { export namespace BB { export const name1 = "fsllala" ; export const age1 = 18 ; } } console .log (AA );
抽离命名空间 1 2 3 4 5 6 7 8 export namespace AA { export namespace BB { export const name1 = "fsllala" ; export const age1 = 18 ; } }
1 2 3 import { AA } from "./index" console .log (AA );
简化命名空间
除了使用 const
还可以使用 import
来简化;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { AA } from "./index" import { AA } from "./index" const method1 = AA .BB .name1 ;console .log (method1); import method2 = AA .BB .name1 ;console .log (`method2:${method2} ` );
in 操作符
如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface Fish { swim (): void } interface Dog { run : () => void } function move (animal: Fish | Dog ) { if ("swim" in animal) { animal.swim (); } else { animal.run (); } } move ({ swim ( ) { console .log ("fish is swiming~~" ); } })
三斜线指令
重点:我们知道在 TS 项目中,文件之间相互引用是通过 import
来实现的,那么声明文件之间的引用呢?没错通过三斜线指令 。
三斜线指令常用的就两个功能:
如果对比 import 的功能,我们可以写出如下的等式:
/// <reference path="..." />
== import filename.xxx
/// <reference types="..." />
== import lodash from "lodash"
PS:下方代码使用 ts-node
输出有有问题,暂未找到解决方法;
But 通过 tsc --outFile sample.js index.ts
将 index.ts
编译成 sample.js
方法可行
1 2 3 4 5 6 7 8 9 10 11 12 13 namespace A {export const fn = ( ) => 'a' } namespace A {export const fn2 = ( ) => 'b' } console .log (A);
声明文件
这里环境为:vite+vue3+ts;
我们之前编写的 typescript 文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方;
还有另外一种文件 .d.ts 文件,它是用来做类型的声明 (declare),称之为类型声明(Type Declaration)或者类型定义(Type Definition)文件。
.d.ts 文件是不需要导出的;让他直接在全局就 ok;
外部定义类型声明 – 第三方库 有一些第三方库是没有写自己的声明文件的,我们在引用的时候,会报错。以 lodash
库为例 (lodash 是有第三方写好的声明文件库的,这里我们自己写)。
安装第三方库
在 vue 文件中进入第三方库,由于没有声明文件,所以会报错。
手写声明文件
这里新建一个 d.ts 文件,例如 fsllala.d.ts
1 2 3 declare module "lodash" ;
然后在 lodash.vue
中发现报错消失了,即可以使用第三方库了 (如果还报错,重启 VS Code 即可);
但是还不知道模块里面有什么东西,所以使用的时候不会有提示。可以在模块内部作用域自定义函数,解决不提示的问题。
1 2 3 4 5 6 declare module "lodash" { export function max (...args:any [] ):any ; export function mean (...args:any [] ):any ; }
1 2 3 4 5 6 <script setup lang="ts" > import _ from "lodash" ;console .log (_.max ([4 , 2 , 8 , 6 ])); console .log (_.mean ([4 , 2 , 8 , 6 ])); </script>
自己代码类型声明
一般来说 TS 的类型是不需要单独抽离到 d.ts 文件中的。但是一些全局声明的变量 / 函数 / 类例外。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const whyName = "Fsllala" ;const whyAge = 18 ;const whyHeight = 188 ;function globalFunc (args ) { return `this is globalFunction~${args} ` ; } function Person (name, age ) { this .name = name; this .age = age; }
上面如果是 JS,就可以直接引用了 (把 script 标签中的 “lang=’ts’” 去掉,就可以用了);显然是 ts 的问题;
1 2 3 4 5 6 7 8 9 10 11 12 declare const whyName : string ;declare const whyAge : number ;declare const whyHeight : number ;declare function globalFunc (args: string ): string ;declare class Person { name : string age : number constructor (name: string , age: number ) }
然后在 lodash.vue
中发现报错消失了,即可以使用全局变量 / 函数 / 类了 (如果还报错,重启 VS Code 即可);
Partial
用于构造一个 Type 下面的所有属性都设置为可选的类型;
1 2 3 4 5 6 7 8 interface IKun { name :string , age :number , hobby :string [] } type IKunOptional = Partial <IKun >;
Record<Keys, Type>
用于构造一个对象类型,它所有的 key (键) 都是 Keys 类型,它所有的 value (值) 都是 Type 类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 interface IKun { name : string , age : number , hobby?: string [] } type t1 = "上海" | "北京" | "洛杉矶" ;type IKuns = Record <t1, IKun >;const ikuns : IKuns = { "上海" : { name : "xxx" , age : 18 }, "北京" : { name : "xxx" , age : 18 }, "洛杉矶" : { name : "xxx" , age : 18 }, }