TypeScript是什么
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 }, }