定义
TypeScript is a typed(静态类型,不同于JS的动态类型) superset of JavaScript that compiles to plain JavaScript(TS不能直接运行,需要先编译成JS).Any browser. Any host. Any OS. Open source.
好处
开发过程中就会发现错误,比如下面的代码JS中显示没问题,TS中就会提示你有潜在的问题,JS只有在编译之后才发现问题
1 2 3 4 function demo (data ) { return Math .sqrt(data.x ** 2 + data.y ** 2 ); } demo({x :3 ,y :'4' });
1 2 3 4 function demo (data: { x: number ; y: number } ) { return Math .sqrt(data.x ** 2 + data.y ** 2 ); } demo({x:3 ,y:'4' });
代码提示
可读性更好
开发环境 :
ts-node可以直接运行TS文件,ts-node xxx.ts,避免先tsc xxx.ts,再node xxx.js。
静态类型深度理解
1 const count: number = 2020 ;
当count具备number的静态类型之后,count会具备number这个类型的所有属性与方法。
1.基础语法 1.1基础类型与对象类型 基础类型
number、boolean、string、null、undefined、symbol、void
对象类型
对象类型{}、数组类型[]、类类型、函数类型
1.2类型注解和类型推断 type annotation
类型注解。我们来告诉TS变量什么类型
type inference
类型推断。TS会自动的去分析变量的类型,如果TS无法分析变量类型,我们就需要使用类型注解。
对于解构赋值的类型注解
1 2 3 4 function add ({ first, second }: { first: number ; second: number } ): number { return first + second; } const total = add({ first: 1 , second: 2 });
赋值和变量的定义在一行的话,类型推断可以有效果。否则的话,类型推断无法推断出来。
1 2 3 4 let count;count = 123 ; ----------------- let count = 123 ;
1 2 3 4 5 6 7 8 const fun1 = (str: string ): number => { return parseInt (str, 10 ); }; const fun2: (str: string ) => number = str => { return parseInt (str, 10 ); };
1.3数组与元组 数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const list: number [] = [1 , 2 , 3 ];const arr: (number |string )[] = [1 , '2' , 3 ];const undefinedArr: undefined [] = [undefined ];const objectArr: {name: string }[] = [{name: 'zj' }]type User = {name: string ,age: number };const obj1Arr: User[] = [{name: 'zj' , age: 1 }];class Teacher { name: string ; age: number ; } const obj2Arr: Teacher[] = [ { name: 'zj' , age: 1 }, new Teacher() ]
1 let list: Array <number > = [1 , 2 , 3 ];
元组
1 2 3 4 5 6 7 8 9 10 11 let x: [string , number ];x = ['hello' , 10 ]; x = [10 , 'hello' ]; const list: [number , number , string ][] = [ [1 , 2 , 'a' ], [2 , 3 , 'b' ] ]
1.4接口 接口定义属性、方法
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 Person { name: string ; age?: number ; } type Person1 = { name: string ; }; const getPersonName = (person: Person ) => { console .log(person.name); }; const setPersonName = (person: Person, name: string ) => { person.name = name; }; const person = { name: 'zj' , sex: 'male' }; getPersonName(person); getPersonName({ name: 'zj' , sex: 'male' });
1 2 3 4 5 6 7 interface Person { name: string ; age?: number ; [propName: string ]: any ; say(): string ; }
接口定义函数
1 2 3 4 5 6 interface SayHi { (word: string ): string ; } const say: SayHi = (word: string ) => { return word; };
类实现接口
1 2 3 4 5 6 7 8 9 10 11 interface Person { name: string ; age?: number ; say(): string ; } class User implements Person { name: 'zj' ; say() { return 'hello' ; } }
接口继承接口
1 2 3 interface Teacher extends Person { teach(): string ; }
1.5类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Person { name: string = 'zhou' ; getName() { return this .name; } } class Teacher extends Person { getTeacherName() { return 'jun' ; } getName() { return super .getName() + this .getTeacherName(); } } const person = new Person();const teacher = new Teacher();console .log(teacher.getName());console .log(teacher.getTeacherName());
修饰符
public
允许在类的内外调用
private
允许在类内调用
protected
允许在类内及继承的子类调用
readonly
构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Person { public name: string = 'zhou' ; constructor (name: string ) { this .name = name; } } const person = new Person('jun' );console .log(person.name);----------------------------------------- class Person { constructor (public name: string ) {} } const person = new Person('jun' );console .log(person.name);
1 2 3 4 5 6 7 8 9 class Person { constructor (public name: string ) {} } class Teacher extends Person { constructor (public age: number ) { super ('zhou' ); } } const teacher = new Teacher(28 );
get和set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { constructor (private _name: string ) {} get name() { return this ._name + ' jun' ; } set name(name: string ) { this ._name = name; } } const person = new Person('zhou' );console .log(person.name);person.name = 'hello' ; console .log(person.name);
静态属性
挂载在类的本身上,而不是类的实例上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Grid { static origin = {x: 0 , y: 0 }; calculateDistanceFromOrigin(point: {x: number ; y: number ;}) { let xDist = (point.x - Grid.origin.x); let yDist = (point.y - Grid.origin.y); return Math .sqrt(xDist * xDist + yDist * yDist) / this .scale; } constructor (public scale: number ) { } } let grid1 = new Grid(1.0 ); let grid2 = new Grid(5.0 ); console .log(grid1.calculateDistanceFromOrigin({x: 10 , y: 10 }));console .log(grid2.calculateDistanceFromOrigin({x: 10 , y: 10 }));
单例模式
1 2 3 4 5 6 7 8 9 10 11 12 class Single { private static instance: Single; private constructor ( ) {} static getInstance() { if (!this .instance) Single.instance = new Single(); return Single.instance; } } const demo1 = Single.getInstance();const demo2 = Single.getInstance();console .log(demo1 === demo2);
抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含abstract关键字并且可以包含访问修饰符。
1 2 3 4 5 6 7 8 9 10 11 12 13 abstract class Geom { width: number = 0 ; getType() { return 'Gemo' ; } abstract getArea(): number ; } class Circle extends Geom { getArea() { return 123 ; } }
1.6联合类型和类型保护 1 2 3 4 5 6 7 8 9 interface Bird { fly: boolean ; sing: () => {}; } interface Dog { fly: boolean ; bark: () => {}; }
类型断言的方式进行类型保护
1 2 3 4 5 6 7 function trainAnimal (animal: Bird | Dog ) { if (animal.fly) { (animal as Bird).sing(); } else { (animal as Dog).bark(); } }
in 语法来做类型保护
1 2 3 4 function trainAnimal (animal: Bird | Dog ) { if ('sing' in animal) animal.sing(); else animal.bark(); }
typeof语法来做类型保护
1 2 3 4 5 6 function add (first: string | number , second: string | number ) { if (typeof first === 'string' || typeof second === 'string' ) { return `${first} ${second} ` ; } return first + second; }
使用instanceof语法来做类型保护
1 2 3 4 5 6 7 8 9 class NumberObj { count: number ; } function add1 (first: object | NumberObj, second: object | NumberObj ) { if (first instanceof NumberObj && second instanceof NumberObj) { return first.count + second.count; } return 0 ; }
1.7枚举类型 1 2 3 4 enum Color {Red, Green, Blue};let c: Color = Color.Blue;console .log(c); console .log(Color[0 ]);
1.8泛型 generic
函数泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function join <T >(first: T, second: T ) { return `${first} ${second} ` ; } function map <T >(params: T[] ) { return params; } function anotherJoin <T , P >(first: T, second: P ) { return `${first} ${second} ` ; } join<string >('1' , '1' ); map<string >(['123' ]); anotherJoin<number , string >(1 , '123' ); anotherJoin('123' ,1 );
泛型类
1 2 3 4 5 6 7 8 9 class DataManager<T> { constructor (private data: T[] ) {} getItem(index: number ): T { return this .data[index]; } } const data = new DataManager<string >(['1' , '2' ]);data.getItem(0 );
泛型约束
1 2 3 4 5 6 7 8 9 10 11 interface Item { name: string ; } class DataManager<T extends Item> { constructor (private data: T[] ) {} getItem(index: number ): string { return this .data[index].name; } } const data = new DataManager([{ name: 'zhou' }]);
keyof的使用
类型可以是字符串
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 Person { name: string ; age: number ; gender: string ; } class Teacher { constructor (private info: Person ) {} getInfo<T extends keyof Person>(key: T): Person[T] { return this .info[key]; } } const teacher = new Teacher({ name: 'zhou' , age: 1 , gender: 'male' }); const test = teacher.getInfo('name' );
2.模块化 2.1命名空间namespace 尽可能少的生成全局变量,把必须暴露出去的export出去,其他的通过namespace封装起来
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 namespace Home { class Header { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is header' ; document .body.appendChild(elem); } } class Content { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is content' ; document .body.appendChild(elem); } } class Footer { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is footer' ; document .body.appendChild(elem); } } export class Page { constructor ( ) { new Header(); new Content(); new Footer(); } } }
模块拆分以及打包到一个文件
components.ts
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 namespace Components { export class Header { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is header' ; document .body.appendChild(elem); } } export class Content { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is content' ; document .body.appendChild(elem); } } export class Footer { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is footer' ; document .body.appendChild(elem); } } }
page.ts
///<reference path="./components.ts" />Home命名空间依赖于Components命名空间
1 2 3 4 5 6 7 8 9 10 11 namespace Home { export class Page { constructor ( ) { new Components.Header(); new Components.Content(); new Components.Footer(); } } }
通过tsc命令并且修改tsconfig.json文件下的:
最终打包成一个js文件:
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 "use strict" ;var Components;(function (Components ) { var Header = (function ( ) { function Header ( ) { var elem = document .createElement('div' ); elem.innerText = 'this is header' ; document .body.appendChild(elem); } return Header; }()); Components.Header = Header; var Content = (function ( ) { function Content ( ) { var elem = document .createElement('div' ); elem.innerText = 'this is content' ; document .body.appendChild(elem); } return Content; }()); Components.Content = Content; var Footer = (function ( ) { function Footer ( ) { var elem = document .createElement('div' ); elem.innerText = 'this is footer' ; document .body.appendChild(elem); } return Footer; }()); Components.Footer = Footer; })(Components || (Components = {})); var Home;(function (Home ) { var Page = (function ( ) { function Page ( ) { new Components.Header(); new Components.Content(); new Components.Footer(); } return Page; }()); Home.Page = Page; })(Home || (Home = {}));
namespace也可以嵌套使用
2.2import components.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export class Header { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is header' ; document .body.appendChild(elem); } } export class Content { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is content' ; document .body.appendChild(elem); } } export class Footer { constructor ( ) { const elem = document .createElement('div' ); elem.innerText = 'this is footer' ; document .body.appendChild(elem); } }
page.ts
1 2 3 4 5 6 7 8 9 import { Header, Content, Footer } from './components' ;export class Page { constructor ( ) { new Header(); new Content(); new Footer(); } }
index.html
需要引入require.js,识别打包成的js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > <script src ="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js" > </script > <script src ="./dist/page.js" > </script > </head > <body > <script > require (['page' ], function (page ) { new page.Page() }) </script > </body > </html >
npm install parcel@next
使用Parcel打包TS代码
3.编写类型描述文件.d.ts .d.ts是为了帮助TS文件更好的理解我们引入的JS文件
3.1全局类型 page.ts文件
1 2 3 4 $(function ( ) { $('body' ).html('<div>123</div>' ); new $.fn.init(); });
juery.d.ts文件
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 interface JqueryInstance { html: (html: string ) => JqueryInstance; } declare function $ (readyFunc: () => void ): void ;declare function $ (selector: string ): JqueryInstance ;declare namespace $ { namespace fn { class init {} } }
3.2模块化变量 ES6
page.ts文件
1 2 3 4 5 6 import $ from 'jquery' ;$(function ( ) { $('body' ).html('<div>123</div>' ); new $.fn.init(); });
juery.d.ts文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 declare module 'jquery' { interface JqueryInstance { html: (html: string ) => JqueryInstance; } function $ (readyFunc: () => void ): void ; function $ (selector: string ): JqueryInstance ; namespace $ { namespace fn { class init {} } } export = $; }
4.高级语法 4.1类的装饰器
装饰器本身是一个函数
类装饰器接受的是构造函数
装饰器执行在类创建的时候
装饰器工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 function testDecorator ( ) { return function (constructor: any ) { constructor .prototype.getName = ( ) => { console .log('zhou' ); }; }; } @testDecorator ()class Test {}const test = new Test();(test as any ).getName();
组合装饰器
当多个装饰器应用在一个声明上时会进行如下步骤的操作:
由上至下依次对装饰器表达式求值
求值的结果会被当作函数,由下至上依次调用
1 2 3 4 5 6 7 8 9 10 11 12 13 function testDecorator1 (constructor: any ) { console .log('decorator1' ); } function testDecorator2 (constructor: any ) { console .log('decorator2' ); } @testDecorator1 @testDecorator2 class Test {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function testDecorator1 ( ) { console .log('decorator1 evaluated' ); return function (constructor: any ) { console .log('decorator1 called' ); }; } function testDecorator2 ( ) { console .log('decorator2 evaluated' ); return function (constructor: any ) { console .log('decorator2 called' ); }; } @testDecorator1 ()@testDecorator2 ()class Test {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function testDecorator ( ) { return function <T extends new (...args: any [] ) => {}>(constructor : T) { return class extends constructor { name = 'jun' ; getName() { return this .name; } }; }; } const Test = testDecorator()( class { name: string ; constructor (name: string ) { this .name = name; } } ); const test = new Test('zhou' );console .log(test);
4.2方法装饰器 方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
成员的属性描述符
类似于Object.defineProperties()
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 function getNameDecorator ( target: any , key: string , descriptor: PropertyDescriptor ) { descriptor.value = () => { return 'decorator' ; }; } class Test { name: string ; constructor (name: string ) { this .name = name; } @getNameDecorator getName() { return this .name; } } const test = new Test('zhou' );console .log(test.getName());
4.3访问器装饰器 不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰器必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符 时,它联合了get和set访问器,而不是分开声明的。
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 function visitDecorator ( target: any , key: string , descriptor: PropertyDescriptor ) { } class Test { private _name: string ; constructor (name: string ) { this ._name = name; } get name() { return this ._name; } @visitDecorator set name(name: string ) { this ._name = name; } } const test = new Test('zhou' );test.name = '12312312' ; console .log(test.name);
4.4属性装饰器 属性装饰器改变属性descriptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function visitDecorator (target: any , key: string ): any { const descriptor: PropertyDescriptor = { writable: false }; return descriptor; } class Test { @visitDecorator name = 'zhou' ; } const test = new Test();test.name = 'jun' ; console .log(test.name);
1 2 3 4 5 6 7 8 9 10 11 12 function visitDecorator (target: any , key: string ): any { target[key] = 'jun' ; } class Test { @visitDecorator name = 'zhou' ; } const test = new Test();console .log(test.name);
4.5参数装饰器 参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
参数在函数参数列表中的索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 function paramDecorator (target: any , method: string , paramIndex: number ) { console .log(target, method, paramIndex); } class Test { getInfo(@paramDecorator name: string , age: number ) { console .log(name, age); } } const test = new Test();test.getInfo('zhou' , 1 );
4.6应用 通过装饰器将异常捕获的内容封装到了一个函数中,并能复用
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 const userInfo: any = undefined ;function catchError (msg: string ) { return function (target: any , key: string , descriptor: PropertyDescriptor ) { const fn = descriptor.value; descriptor.value = function ( ) { try { fn(); } catch (error) { console .log(msg); } }; }; } class Test { @catchError ('userInfo.name不存在' ) getName() { return userInfo.name; } @catchError ('userInfo.age不存在' ) getAge() { return userInfo.age; } } const test = new Test();test.getName(); test.getAge();
1 2 3 4 5 6 7 8 9 10 import 'reflect-metadata' ;const user = { name: 'zhou' }; Reflect.defineMetadata('data' , 'test' , user); console .log(Reflect.getMetadata('data' , user));
可以在类 、类的属性 以及类的方法 上定义元数据
1 2 3 4 5 6 7 8 9 10 11 12 13 @Reflect .metadata('data' , 'test' )class User { name = 'zhou' ; } console .log(Reflect.getMetadata('data' , User));--------------------- class User { @Reflect .metadata('data' , 'test' ) name = 'zhou' ; } console .log(Reflect.getMetadata('data' , User.prototype, 'name' ));
1 2 3 4 5 6 7 8 9 10 11 class User { @Reflect .metadata('data' , 'test' ) getName() {} } class Teacher extends User {}console .log(Reflect.hasOwnMetadata('data' , User.prototype, 'getName' ));console .log(Reflect.hasOwnMetadata('data' , Teacher.prototype, 'getName' ));
API
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 Reflect.defineMetadata(metadataKey, metadataValue, target); Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey); let result = Reflect.hasMetadata(metadataKey, target);let result = Reflect.hasMetadata(metadataKey, target, propertyKey);let result = Reflect.hasOwnMetadata(metadataKey, target);let result = Reflect.hasOwnMetadata(metadataKey, target, propertyKey);let result = Reflect.getMetadata(metadataKey, target);let result = Reflect.getMetadata(metadataKey, target, propertyKey);let result = Reflect.getOwnMetadata(metadataKey, target);let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);let result = Reflect.getMetadataKeys(target);let result = Reflect.getMetadataKeys(target, propertyKey);let result = Reflect.getOwnMetadataKeys(target);let result = Reflect.getOwnMetadataKeys(target, propertyKey);let result = Reflect.deleteMetadata(metadataKey, target);let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);@Reflect .metadata(metadataKey, metadataValue)class C { @Reflect .metadata(metadataKey, metadataValue) method() { } }
4.8装饰器的执行顺序
当多个装饰器应用在一个声明上时会进行如下步骤的操作:
由上至下依次对装饰器表达式求值;
求值的结果会被当作函数,由下至上依次调用.
不同装饰器的执行顺序:属性装饰器 > 方法装饰器 > 参数装饰器 > 类装饰器
自封装的元数据装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import 'reflect-metadata' ;function showData (target: typeof User ) { for (let key in target.prototype) { const data = Reflect.getMetadata('data' , target.prototype, key); console .log(data); } } function setData (dataKey: string , msg: string ) { return function (target: User, key: string ) { Reflect.defineMetadata(dataKey, msg, target, key); }; } @showData class User { @Reflect .metadata('data' , 'name' ) getName() {} @setData ('data' , 'age' ) getAge() {} }