TypeScript入门

作者: oWSQo | 来源:发表于2017-09-18 21:45 被阅读1706次

    概述

    TypeScript本质上是向JavaScript语言添加了可选的静态类型和基于类的面向对象编程,同时也支持诸如接口、命名空间、装饰器等特性,它相当于是JavaScript的超集。


    ES5、ES6和TypeScript的关系

    安装

    npm install -g typescript
    

    在浏览器中要运行TypeScript程序,必须先编译成浏览器能识别的JavaScript代码,可以通过tsc编译器来编译TypeScript文件,生成与之对应的JavaScript文件,编译过程如下:

    tsc hello.ts
    

    此时会在目录下看到一个hello.js文件,该文件能在浏览器中运行。

    基本类型

    • 布尔类型(boolean
    • 数字类型(number
    • 字符串类型(string
    • 数组类型(array
    • 元组类型(tuple
    • 枚举类型(enum
    • 任意值类型(any
    • nullundefined
    • void类型
    • never类型

    在TypeScript中声明变量,需要加上类型声明。通过静态类型约束,在编译时执行类型检查,这样可以避免一些类型混用的低级错误。

    布尔类型

    let flag:boolean=true;
    flag=1;//报错
    

    数字类型

    在TypeScript中,数字都是浮点型。TypeScript同时支持二进制、八进制、十进制和十六进制字面量。

    let binaryLiteral:number=0b1010;//二进制
    let octalLiteral:number=0o744;//八进制
    let decLiteral:number=6;//十进制
    let hexLiteral:number=0xf00d;//十六进制
    

    字符串类型

    TypeScript支持使用模板字符串反引号(`)来定义多行文本和内嵌表达式。使用${expr}的形式嵌入变量或表达式,在处理拼接字符串的时候很有用。

    let name:string="angular";
    let words:string=`你好,今年是${name}`发布6周年`;
    

    数组类型

    TypeScript有两种数组定义方式。

    let arr:number[]=[1,2];
    let arr:Array<number>=[1,2];
    

    元组类型

    元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同。

    let x:[string,number];
    x=['angular',25];
    x=[10,'angular'];//报错
    console.log(x[0]);//输出angular
    

    枚举类型

    枚举是一个可被命名的整型常数的集合,枚举类型为集合成员赋予有意义的名称,增强可读性。

    enum Color {Red,Green,Blue};
    let c:Color=Color.Blue;
    console.log(c);//输出2
    

    枚举默认下标是0,可以手动修改默认下标值。

    enum Color {Red=2,Blue,Green=6};
    let c:Color=Color.Blue;
    console.log(c);//输出3
    

    任意值类型

    任意值是TypeScript针对编程时类型不明确的变量使用的一种数据类型,它常用于以下三种情况。

    • 变量的值会动态变化时,比如来自用户的输入,任意值类型可以让这些变量跳过编译阶段的类型检查。
    let x:any=1;
    x="angular";
    x=false;
    
    • 改写现有代码时,任意值允许在编译阶时可选择地包含或移除类型检查。
    let x:any=4;
    x.ifItExists();//正确,ifItExists方法在运行时可能存在,但这里并不检查
    x.toFixed();//正确
    
    • 定义存储各种类型数据的数组时。
    let arrayList:any[]=[1,false,"fine"];
    arrayList[1]=100;
    

    null和undefined

    默认情况下,nullundefined是其他类型的子类型,可以赋值给其他类型,赋值后的类型会变成nullundefined。在TypeScript中启用严格的空校验特性,就可以使nullundefined只能被赋值给void或本身对应的类型。

    //启用--strictNullChecks
    let x:number;
    x=1;
    x=undefined;//运行错误
    x=null;//运行错误
    

    如果一个类型可能出现nullundefined,可以用|来支持多种类型。

    let x:number|null|undefined;
    

    void类型

    使用void表示没有任何类型。例如一个函数没有返回值时,意味着返回值类型是void

    function hell():void {
        alert("void");
    }
    

    对于可忽略返回值的回调函数来说,使用void类型比任意值类型更安全。

    function func(foo:()=>void) {
        let f=foo();//使用函数foo的返回值
        f.doSth();//报错,void类型不错在doSth()方法,换成任意值类型则不报错
    }
    

    never类型

    never是其他类型(包括nullundefined)的子类型,代表从不会出现的值。这意味着声明为never类型的变量只能被never类型所赋值,在函数中它通常表现为抛出异常或无法执行到终止点(如无限循环)。

    let x:never;
    let y:number;
    x=123;//运行错误,数字类型不能转为never类型
    x=(()=>{throw neew Error('exception occur')});//运行正确,never类型可赋值给never类型
    x=(()=>{throw neew Error('exception occur')});//运行正确
    function loop():never {
        while(true) {
        }
    } //返回值为never的函数可以是无法被执行到终止点的情况
    //返回值为never的函数可以是抛出异常的情况
    function error(message:string):never {
        throw new Error(message)
    }
    

    声明和解构

    TypeScript支持varletconst这样的声明方式。

    let声明

    let声明的变量只在块级作用域内有效。

    function f(input:boolean) {
        let a=100;
        if(input){
            let b=a+1;
            return b;
        }
        return b;//错误,b没有定义
    }
    

    块级作用域还有一个问题,就是变量不能在它声明之前被读取或赋值。

    a++;//错误
    let a;
    

    在相同作用域中,let不允许变量被重复声明。

    let x=2;
    let x=3; //报错
    

    此外,还需要注意let声明在下面两种函数入参的对比。

    function funA(x){
        let x=100;//报错,x已经在函数入参声明
    }
    //增加了判断条件组成的新的块级作用域
    function funB(condition,x) {
        if(condition){
            let x=100;//运行正常
            return x;
        }
        return x;
    }
    funB(false,0);//返回0
    funB(true,0);//返回100
    

    const声明

    constlet拥有相同的作用域规则,但const声明的是常量,常量不能被重新赋值,否则将编译错误。但如果定义的常量是对象,对象里的属性值是可以被重新赋值的。

    const CAT_LIVES_NUM=9;
    const kitty={
        name:"aurora",
        numLives:CAT_LIVES_NUM
    };
    kitty={
        name:"Danielle",
        numLives:CAT_LIVES_NUM
    };//错误
    kitty.name="kitty";//正确
    kitty.numLives--;//正确
    

    解构

    所谓解构,就是将声明的一组变量与相同结构的数组或者对象的元素数值一一对应,并将变量相对应元素进行赋值。TypeScript支持数组解构和对象解构。

    数组解构
    let input=[1,2];
    let [first,second]=input;
    console.log(second);//相当于input[1]=2
    

    也可作用于已声明的变量。

    [first,second]=[second,first];//变量交换
    

    或作用于函数参数。

    function f([first,second]=[number,number]){
        console.log(first+second);
    }
    f([1,2]);//输出3
    

    还可以在数组解构中使用rest参数语法创建一个剩余变量列表,“...”三个连续小数点表示展开操作符,用于创建可变长的参数列表。

    let [first,...rest]=[1,2,3,4];
    console.log(first);//1
    console.log(rest);//[2,3,4]
    
    对象解构
    let test={x:0,y:10,width:15,height:20};
    let {x,y,width,height};
    console.log(x,y,width,height);//0,10,15,20
    

    函数

    函数定义

    TypeScript支持函数声明和函数表达式的写法。

    //函数声明写法
    function maxA(x:number,y:number):number {
        return x>y?x:y;
    }
    //函数表达式
    let maxB=function(x:number,y:number):number {
        return x>y?x:y;
    }
    

    可选参数

    在JavaScript中,被调函数的每个参数都是可选的,在TypeScript中,被调函数的每个参数都是必传的,在编译时,会检查函数每个参数是否传值。

    function max(x:number,y:number):number {
        return x>y?x:y;
    }
    let result1=max(2);//报错
    let result2=max(2,4);//运行正常
    

    TypeScript提供了可选参数语法,即在参数名旁加上?来使其变成可选参数。

    function max(x:number,y?:number):number {
        if(y){
            return x>y?x:y;
        }else{
            return x;
        }
    }
    let result1=max(2);//运行正常
    let result2=max(2,4);//运行正常
    

    可选参数必须位于必选参数的后面。

    默认参数

    TypeScript还支持初始化默认参数。如果函数的某个参数设置了默认值,当该函数被调用时,如果没有给这个参数传值或者传的值为undefined时,这个参数的值就是设置的默认值。

    function max(x:number,y=4):number {
        return x>y?x:y;
    }
    let result1=max(2);//运行正常
    let result2=max(2,undefined);//运行正常
    let result1=max(2,4);//运行正常
    

    带默认值的参数不必放在必选参数的后面,但如果默认值参数放到了必选参数的前面,用户必须显式地传入undefined

    剩余参数

    当需要同时操作多个参数,或者并不知道会有多少参数传递进来时,就需要用到剩余参数。在TypeScript中,所有的可选参数都可以放到一个变量里。

    function sum(x:number,...restOfNumber:number[]):number {
        let result=x;
        for(let i=0;i<restOfNumber.length;i++){
            result+=restOfNumber[i];
        }
        return result;
    }
    let result=sum(1,2,3,4,5);
    console.log(result);//输出15
    

    剩余参数包含的参数个数可以为0到多个。

    函数重载

    函数重载通过为同一个函数提供多个函数类型定义来达到实现多种功能的目的。TypeScript可以支持函数的重载。

    function css(config:{});
    function css(config:string,value:string);
    function css(cibfug:any,value?:any){
        if(typeof config=='string'){
            ...
        }else if(typeof config=='object'){
            ...
        }
    }
    

    箭头函数

    TypeScript提供的箭头函数(=>)在函数创建时就绑定了this,而不是在函数调用时。

    let gift=[
        gifts:["one","two","three","four","five"],
        giftPicker:function(){
            return ()=>{
                let pickedNumber=Math.floor(Math.random()*6);
                return this.gifts[pickedNumber];
            }
        }
    ]
    let pickGift=gift.giftPicker();
    console.log("you get a"+pickGift());
    

    类的例子

    class Car {
        engine:string;
        constructor(engine:string){
            this.engine=engine;
        }
        drive(distanceInMeters:number=0){
            console.log(`A car runs ${distanceInMeters}m powered by`+this.engine);
        }
    }
    let car =new Car("petrol");
    car.drive(100);//A car runs 100m powered by petrol
    

    继承与多态

    封装、继承和多态是面向对象的三大特征。类就是封装。在TypeScript中,使用extends关键字可实现继承。

    //继承自前面的Car类
    class MotoCar extends Car {
        constructor(engine:string) {super(engine);}
    }
    class Jeep extends Car {
        constructor(engine:string) {super(engine);}
        drive(distanceInMeters:number=100){
            console.log("Jeep");
            return super.drive(distanceInMeters);
        }
    }
    let tesla =new MotoCar("electricity");
    let landRover:Car=new Jeep("petrol");//实现多态
    tesla.drive();//调用父类的drive()方法
    landRover.drive(200);//调用子类的drive()方法
    

    修饰符

    三种修饰符:公共(public)、私有(private)和受保护(protected)。

    public修饰符

    在TypeScript中,每个成员默认为public,可以被自由地访问。

    private修饰符

    当成员被标记成private时,就不能在类的外部访问它。

    class Car {
        private engine:string;
        constructor(engine:string){
            this.engine=engine
        }
    }
    new Car("petrol").engine;//报错,engine属性是私有的
    
    protected修饰符

    当类成员被标记成protected时,不能在类的外部访问它,但可以在派生类中访问。

    class Car {
        protected engine:string;
        constructor(engine:string){
            this.engine=engine;
        }
        drive(distanceInMeters:number=0){
            console.log(`A car runs ${distanceInMeters}m powered by`+this.engine);
        }
    }
    class MotoCar extends Car {
        constructor(engine:string) {super(engine);}
        drive(distanceInMeters:number=50){
            super.drive(distanceInMeters)
        }
    }
    let tesla=new MotoCar("electricity");
    console.log(tesla.drive());//A car runs 50m powered by electricity
    console.log(tesla.engine);//报错
    

    参数属性

    参数属性是通过给构造函数参数添加一个访问限定符(publicprivateprotected)来声明。

    class Car {
        constructor(protected engine:string){}
        drive(distanceInMeters:number=0){
            console.log(`A car runs ${distanceInMeters}m powered by`+this.engine);
        }
    }
    

    静态属性

    类的静态成员存在于类本身而不是类的实例上,类似在实例属性上使用this.来访问属性,我们使用类名. 来访问静态属性。可以使用static关键字来定义类的静态属性。

    class Grid{
        static origin={x:0,y:0};
        constructor(public scale:number){}
        calculateDistanceFromOrigin(point:{x:number;y:number}){
            let xDist=(point.x-Grid.origin.x);
            let yDist=(point.y-Grid.origin.y);
            return Math.sqrt(xDist*yDist*yDist)/this.scale;
        }
    }
    let grid=new Grid(1.0);
    console.log(grid.calculateDistanceFromOrigin({x:10,y:10}));
    //输出14.1421356
    

    抽象类

    TypeScript有抽象类的概念,它是供其它类继承的基类,不能直接被实例化。抽象类必须包含一些抽象方法,同时也可以包含非抽象的成员。abstract关键字用于定义抽象类和抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。

    abstract class Person {
      abstract speak():void;//必须在派生类中实现
      walking(): void {
        console.log('walking on the road');
      }
    
      class Male extends Person {
        speak(): void{
          console.log('How are you?');
        }
      }
    }
    let person:Person;  //创建一个抽象类引用
    person=new Person(); //报错:不能创建抽象类实例
    person=new Male();   //创建一个Male实例
    person.speak();
    person.walking();
    

    模块

    模块是自声明的,两个模块之间的关系是通过在文件级别上使用importexport来建立的。TypeScript和ES6一样,任何包含顶级importexport的文件都会被当成一个模块。
    模块在自身的作用域里执行,而不是在全局作用域里,这意味着定义在一个模块里的变量、函数和类等,在模块外是不可见的,除非明确地使用export导出它们。如果想使用其他模块导出的变量、函数、类和接口时,必须先通过import导入它们。
    模块使用模块加载器去导入它的依赖,模块加载器在代码运行时会查找并加载模块间的所有依赖。在Angular中,常见的模块加载器有SystemJS和Webpack。

    模块导出方式

    导出方式有三种。

    导出声明

    任何模块都能通过export关键字来导出。

    export const COMPANY="GF";//导出变量
    export interface IdentityValidate{  //导出接口
        isGfStaff(s: string): boolean;
    }
    export class ErpIdentityValidate implements IdentityValidate { //导出类
        isGfStaff(erp: string) {
            return erpService.contains(erp);  //判断是否为内部员工
        }
    }
    
    导出语句

    当我们需要对导出的模块进行重命名时,就用到了导出语句。

    class ErpIdentityValidate implements IdentityValidate { //导出类
        isGfStaff(erp: string) {
            return erpService.contains(erp);  
        }
    }
    export { ErpIdentityValidate };
    export { ErpIdentityValidate as GFIdentityValidate };
    
    模块包装

    在需要修改和扩展已有的模块,并导出供其他模块调用时,可以使用模块包装来再次导出。

    //导出原先的验证器,但做了重命名   
    export { ErpIdentityValidate as RegExpBasedZipCodeValidator} from "./ErpIdentityValidate";
    

    一个模块可以包裹多个模块,并把新内容以一个新的模块导出。

    export * from "./IdentityValidate";
    export * from "./ErpIdentityValidate";
    

    模块导入方式

    模块导入与模块导出相对应,可以使用import关键字来导入当前模块依赖的外部模块。导入方式有两种。

    导入一个模块
    import { ErpIdentityValide } from "./ErpIdentityValidate";
    let erpValide = new ErpIdentityValidate();
    
    别名导入
    import { ErpIdentityValide as ERP } from "./ErpIdentityValidate";
    let erpValide = new ErpIdentityValidate();
    

    也可以对整个模块进行别名导入,将整个模块导入到一个变量,并通过它来访问模块的导出部分。

    import * as validator from "./ErpIentityValidate";
    let myValidate= new validator.ErpIdentityValidate();
    

    模块的默认导出

    模块可以用default关键字实现默认导出的功能,每个模块可以有一个默认导出。类和函数声明可以直接省略导出名来实现默认导出。

    //默认导出类
    //ErpIdentityValidate.ts
    export default class ErpIdentityValidate implements IdentityValidate {
        isGfStaff(erp:string){
            return erpService.contains(erp);
        }
    }
    //test.js
    import validator from "./ErpIdentityValidate";
    let erp = new validator();
    //默认导出函数
    // nameServiceValidate.ts
    export default function (s: string) {
        return nameService.contains(s);
    }
    //test.js
    import validator from "./nameValidate";
    let name = "Angular";
    //使用导出函数
    console.log(`"${nmae}" ${ validate(name) ? "matches" : " does not match"}`);
    //默认导出值
    // constantService.ts
    export default "Angular";
    //test.ts
    import name from "./constantService";
    console.log(name);//"Angular"
    

    模块设计原则

    尽可能的在顶层导出

    顶层导出可以降低调用方使用的难度,所以尽量使用默认导出或顶层导出。

    //ClassTest.ts
    export default class ClassTest {
      // ...
    }
    //FuncTest.ts
    export default function FuncTest() {
      // ...
    }
    //Test.ts
    import ClassTest from "./ClassTest";
    import FuncTest from "./FuncTest";
    let C=new ClassTest();
    FuncTest();
    

    返回多个对象的时候,可以采用顶层导出的方式,调用的时候再明确地列出导入的对象名称即可。

    //MouduleTest.ts
    export class ClassTest() {
      // ...
    }
    export FuncTest() {
      // ...
    }
    //Test.ts
    import { ClassTest,FunctTest} from "./ModuleTest";
    let C=new ClassTest();
    FuncTest();
    
    明确的列出导入的名字

    在导入的时候尽可能明确地指定导入对象的名称,这样只要接口不变,调用方式就可以不变,从而降低了导入跟导出模板的耦合度,做到面向接口编程。

    import { ClassTest,FunctTest} from "./ModuleTest";
    
    使用命名空间模式导出
    // MyLargeModule.ts
    export class Dog {/* ... */ }
    export class Cat {/* ... */ }
    export class Tree {/* ... */ }
    export class Flower {/* ... */ }
    //Consumer.ts
    import* as myLargeModule from "./MyLargeModule.ts";
    let x= new myLargeModule.Dog();
    
    使用模块包装进行扩展

    我们可能经常要去扩展一个模块的功能,推荐的方案是不要去改变原来的对象,而是导出一个新的对象来提供新的功能。

    // ModuleA.ts
    export class ModuleA {
        constructor() {/* ... */}
        sayHello() {
            // ...
        }
    }
    // ModuleB.ts
    import { ModuleA } from "./ModuleA.ts"
    class ModuleB extends ModuleA {
      constructor() { super(); /* ... */ }
      //添加新的方法
      sayHi() {
        // ...
      }
    }
    //Test.ts
    import { ModuleA } from "./ModuleB";
    let C=new ModuleA();
    

    接口

    属性类型接口

    在TypeScript中,使用interface关键字来定义接口

    interface FullName {
      firstName: string;
      secondName: string;
    }
    function printLable(name: FullName) {
      console.log(name.firstName + " "+name.secondName);
    }
    let myObj = {age:10,firstName:"Jim",secondName:"Raynor"};
    printLable(myObj);
    

    TypeScript还提供了可选属性,可选属性对可能存在的属性进行预定义,并兼容不传值的情况。带有可选属性的接口在定义的可选属性变量名后面加一个“?”。

    interface FullName {
      firstName: string;
      secondName?: string;
    }
    function printLable(name: FullName) {
      console.log(name.firstName + " "+name.secondName);
    }
    let myObj = {firstName:"Jim"};  //secondName 是可选属性,可以不传
    printLable(myObj);  //输出: Jim undefined
    

    函数类型接口

    定义函数类型接口时,需要明确定义函数的参数列表和返回值类型,且参数列表的每个参数都要有参数名和类型。

    interface encrypt {
      (val:string,salt:string):string
    }
    

    使用函数类型接口。

    let md5: encrypt;
    md5=function (val:string,salt:string) {
      console.log("origin value:" +val);
      let encryptValue =doMd5(val,salt);//doMd5只是个mock方法
      console.log("encrypt value:" +encryptValue);
      return encryptValue;
    }
    let pwd =md5("password","Angular");
    

    使用函数类型的接口需要注意:

    • 函数的参数名:使用时参数个数需与接口定义的参数相同,对应位置变量的数据类型需保持一致,参数名可以不一样
    • 函数返回值:函数的返回值类型与接口定义的返回值类型要一致

    可索引类型接口

    可索引类型接口用来描述那些可以通过索引得到的类型,如userArray[1]userObject["name"]。它包含一个索引签名,表示用来索引的类型与返回值类型,即通过特定的索引来得到指定类型的返回值。

    interface UserArray {
      [index: number]: string;
    }
    interface UserObject {
      [index: string]: string;
    }
    let userArray: UserArray;
    let UserObject: UserObject;
    userArray =["张三","李四"];
    userObject =["name","张三"];
    console.log(userArray[0]);  //输出: 张三
    console.log(userObject["name"]);  //输出: 张三
    

    索引签名支持字符串和数字两种数据类型。使用这两种类型的最终返回值可以使一样的,即使是用数字类型时,JavaScript最终也会将它转换成字符串类型
    然后再去索引对象的。

    console.log(userArray[0]);  //输出: 张三
    console.log(userArray["0"]);  //输出: 张三
    

    类类型接口

    类类型接口用来规范一个类的内容。

    interface Animal {
      name: string;
    }
    class Dog implements Animal {
        name: string;
        constructor(n: string) {}
    }
    

    我们可以在接口中描述一个方法,并在类里去具体实现它的功能。

    interface Animal {
     name: string;
     setName(n: string): void;
    }
    class Dog implements Animal {
        name: string;
        setName(n: string) {
            this.name=n;
        }
        constructor(n: string) {}
    }
    

    接口扩展

    和类一样,接口也可以实现相互扩展,即能将成员从一个接口复制到一个页面,这样可以更灵活地将接口拆分到可复用的模板里。

    interface Animal {
        eat(); void;
    }
    interface Person extends Animal {
        talk(); void;
    }
    class Programmer {
        coding():void{
            console.log('wow,TypeScript is the best language');
        }
    }
    class ITGirl extends Programmer implements Person {
        eat() {
            console.log('animal eat');
        }
        talk(){
          console.log('person talk');
        }
        coding():void {
         console.log('I am girl,but i like coding.');
        }
    }
    let itGirl =new ITGirl(); //通过组合集成类来实现接口扩展,可以更灵活复用模块
    itGirl.coding();
    

    装饰器

    装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、熟悉或参数上。装饰器由@符号紧接一个函数名称,形如@expressionexpression求值后必须是一个函数,在函数执行的时候装饰器的声明方法会被执行。
    有4种类型的装饰器:方法装饰器、类装饰器、参数装饰器和属性装饰器。

    方法装饰器

    方法装饰器是在声明一个方法之前被声明的,它会被应用到方法的属性描述符上,可以用来监视、修改或替换方法定义。
    方法装饰器的声明:

    declare type MethodDecorator = <T>(target:Object,propertyKey:string|symbol,descriptor:TypedPropertyDescriptor<T>)=>TypedPropertyDescriptor<T>|void;
    

    方法装饰器表达式会在运行时当作函数被调用,传入下列三个参数:

    • target:类的原型对象
    • propertyKey:方法的名字
    • descriptor:成员属性描述

    descriptor的类型为TypedPropertyDescriptor,在TypeScript中定义如下:

    interface TypedPropertyDescriptor<T>{
        enumerable?:boolean;//是否可遍历
        configurable?:boolean;//属性描述是否可改变或属性是否可删除
        writable?:boolean;//是否可修改
        value?:T;//属性的值
        get?:()=>T;//属性的访问器函数(getter)
        set?:(value:T)=>void;//属性的设置器函数(setter)
    }
    

    方法装饰器例子:

    class TestClass{
        @log
        testMethod(arg:string){
            retrun "logMsg:"+arg;
        }
    }
    function log(target:Object,propertyKey:string,descriptor:TypedPropertyDescriptor<any>){
        let origin=descriptor.value;
        descriptor.value=function(...args:any[]){
            console.log("args:"+JSON.stringify(args)); //调用前
            let result=origin.apply(this,args); //调用方法
            console.log("The result-"+result); //调用后
            return result; //返回结果
        };
        return descriptor;
    }
    new TestClass().testMethod("test method decorator");
    输出结果:
    args:["test method decorator"]
    The result-logMsg:test method decorator
    

    当方法装饰器@log被调用时,它会打印日志信息。

    类装饰器

    类装饰器是在声明一个类之前被声明的,它应用于类构造函数,可以用来监视、修改或替换类定义,在TypeScript中定义如下:

    declare type ClassDecorator=<TFunction extends Function>(target:TFunction)=>TFunction|void;
    

    类的构造函数作为其唯一的参数。类装饰器在运行时会被当作函数的形式来调用。
    假如类装饰器返回了一个值,那么它会在构造函数中替换类的声明。

    @Component({
        selector:'person',
        template:'person.html'
    })
    class Person{
        constructor(
            public firstName:string,
            public secondName:string
        ){}
    }
    

    装饰器@Component的定义:

    function Component(component){
        return (target:any)=>{
            return componentClass(target,component);
        }
    }
    //componentClass的实现
    function componentClass(target:any,component?:any):any{
        var original=target;
        function construct(constructor,args){ //处理原型链
            let c:any=function(){
                return constructor.apply(this,args);
            }
            c.prototype=constructor.prototype;
            return new c;
        }
        let f:any=(...args)=>{
            console.log("selector:"+component.selector);
            console.log("template:"+component.template);
            console.log(`Person:${original.name}(${JSON.stringify(args)})`);
            return construct(original,args);
        };
        f.prototype=original.prototype;
        return f;//返回构造函数
    }
    let p=new Person("angular","js");
    //输出结果:
    selector:person
    template:person.html
    Person:Person(["angular","js"])
    

    参数装饰器

    参数装饰器是在声明一个参数之前被声明的,它用于类的构造函数或方法声明。参数装饰器在运行时会被当作函数的形式来调用。

    declare type ParameterDecorator=(target:Object,propertyKey:string|symbol,parameterIndex:number)=>void;
    
    • target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • propertyKey:参数名称
    • parameterIndex:参数在函数参数列表中的索引
    class userService{
        login(@inject name:string) {}
    }
    //@inject装饰器的实现
    function inject(target:any,propertyKey:string|symbol,parameterIndex:number){
        console.log(target); //userService prototype
        console.log(propertyKey);
        console.log(parameterIndex);
    }
    //输出结果:
    Object   login   0
    

    属性装饰器

    属性装饰器定义:

    declare type PropertyDecorator=(target:Object,propertyKey:string|symbol)=>void;
    
    • target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    • propertyKey:属性名字

    属性装饰器是用来修饰类的属性,声明和被调用方式跟其他装饰器类似。

    装饰器组合

    TypeScript支持多个装饰器同时应用到一个声明上。

    @decoratorA @decoratorB param
    //或
    @decoratorA
    @decoratorB
    functionA
    

    在TypeScript中,当多个装饰器应用在同一个声明上的时候,会进行如下步骤:

    • 从左到右(从上到下)依次执行装饰器函数,得到返回结果
    • 返回结果会被当作函数,从左到右(从上到下)依次调用
    function Component(component){
        console.log('selector:'+component.selector);
        console.log('template:'+component.template);
        console.log('component init');
        return (target:any)=>{
            console.log('component call');
            return target;
        }
    }
    function Directive(directive){
        console.log('directive init');
        return (target:any)=>{
            console.log('directive call');
            return target;
        }
    }
    @Component({
        selector:'person',
        template:'person.html'
    })
    @Directive({
        class Person {}
    })
    let p=new Person();
    //输出结果:
    selector:person
    template:person.html
    component init
    directive init:
    component call
    directive call
    

    泛型

    在实际开发中,我们定义的API不仅仅需要考虑功能是否健全,还需要考虑到它的复用性,更多的时候需要支持不特定的数据类型,而泛型就是用来实现这样的效果。
    比如有个最小堆算法,需要同时支持数字和字符串两种类型,可以通过把集合类型改为任意值类型(any)来实现,但这样就等于放弃了类型检查,我们希望的是返回的类型需要和参数类型一致。

    class MinHeap<T>{
        list:T[]=[];
        add(element:T):void{
            //这里进行大小比较,并将最小值放在数组头部
        }
        min():T{
            return this.list.length?this.list[0]:null;
        }
    }
    let heap1=new MinHeap<number>();
    heap1.add(3);
    heap1.add(5);
    console.log(heap1.min());
    let heap2=new MinHeap<string>();
    heap2.add('a');
    heap2.add('z');
    console.log(heap2.min());
    

    泛型也支持函数。

    function zip<T1,T2>(l1:T1[],l2:T2[]):[T1,T2][]{
        let len=Math.min(l1.length,l2.length);
        let ret=[];
        for(let i=0;i<len;i++){
            ret.push([l1[i],l2[i]])
        }
        return ret;
    }
    console.log(zip<number,string>([1,2,3],['Jim','Sam','Tom']));
    

    其它

    编译配置文件

    tsc编译器有很多命令行参数。tsconfig.json文件使得编译参数能在文件中维护。当运行tsc时,编译器从当前目录向上搜索tsconfig.json文件来加载配置。

    声明文件

    JavaScript没有静态类型检查的功能。而TypeScript编译器也只提供了ECMAScript标准里的标准库类型声明,只能识别TypeScript代码中的类型。如果想引用第三方JS库,如jQuerylodash等,就需要使用声明文件来辅助开发。在TypeScript中,声明文件是以.d.ts为后缀的形式存在的,其作用是描述一个JS模块文件所有导出的接口类型信息。
    直接使用npm获取声明文件:

    npm install --save @types/lodash
    
    import * as _ from "lodash";
    _.padStart("Hello",2," ");
    

    如果配置了tsconfig.json文件,也可以直接使用全局变量“_”。

    _.padStart("Hello",2," ");
    

    tsconfig.json文件中可以使用typeRoots,来指定允许查找的包得文件路径。

    {
        "compilerOptions":{
            "typeRoots":["./typings"]
        }
    }
    

    上面的配置表示编译器会去查找所有./typings目录下的包,但不包含./node_modules/@types里面的包。
    如果在tsconfig.json中配置了types,那么只有配置的包才会被包含进来。

    {
        "compilerOptions":{
            "types":["lodash","koa"]
        }
    }
    

    另外可以通过设置"types":[]来禁止自动引入@type的包。

    相关文章

      网友评论

      • 鱼Fcn:您好,请问可以转载吗?

      本文标题:TypeScript入门

      本文链接:https://www.haomeiwen.com/subject/urradxtx.html