揭秘Angular 2(一):TypeScript入门

来源:互联网 发布:淘宝客没权重吗 编辑:程序博客网 时间:2024/05/21 22:54

3.1 TypeScript概述

3.1.1概述

3.1.2安装

  • 检查npm是否已经安装
  • 接下来我们使用2.0正式版的TypeScript,安装命令如下:

    npm install -g typescript@2.0.0
  • 写第一个Typescript程序,并保存到hello.ts中,文件中代码:

    console.log('hello TypeScript!');
  • 通过tsc来编译TypeScript文件:

    tsc hello.ts    

3.2基本类型

  • 布尔类型(boolean)
  • 数字类型(number)
  • 字符串类型(string)
  • 数组类型(array)
  • 元祖类型(typule)
  • 枚举类型(enum)
  • 任意值类型(any)
  • null和undefined
  • void类型
  • never类型

3.2.1 布尔类型

let flag:boolean=true;flag=1;//报错,不能把数字类型的值赋给布尔类型的变量

3.2.2 数字类型

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

3.2.3 字符串类型

let name:string=”Angular”; 
let years:number=5; 
let words:string=你好,今年是 ${name} 发布 ${ years +1} 周年;

3.2.4 数组类型

// 在元素类型后面接上[]let arr:number[]=[1,2];//或者使用数组泛型let arr:Array<number>=[1,2];

3.2.5 元组类型

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

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

3.2.6 枚举类型

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

    enum Color {Red,Green,Blue};let c:Color=Color.Blue;console.log(c); //输出:2
  • 枚举默认下标是0,可以手动修改默认下标值:

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

3.2.7 任意值类型

  • 任意值是TypeScript针对编程时类型不明确的变量使用的一种数据类型:

    • 变量的值会动态变化时

      let x:any=1;//数字类型x="I am a string"; //字符串类型x=false;//布尔类型
    • 改写现有代码时,任意值允许在编译时可选择地包含或移除类型检查

      let x:any=4;x.ifItExists();//正确,ifItExists方法在运行时可能存在,但是这里并不检查x.toFixed();//正确
    • 定义存储各种类型数据的数组时:

      let arrayList: any[]=[1,false,"fine"];arrayList[1]=100;

3.2.8 null和undefined

  • TypeScript中启用严格的空校验(--strictNullChecks)特性,nullundefined只能被赋值给void或本身对应的类型: 
    //启用 –stricNullChecks 
    let x:number; 
    x=1; //运行正确 
    x=undefined; //运行错误 
    x=null; //运行错误

    • 上面例子中变量x只能是数字类型,如果可能出现null或者undefined,可以用|来支持多种类型

      //启用 --stricNullCheckslet x:number;let y:number | undefinedlet z:number | null | undefinedx=1; //运行正确y=1; //运行正确z=1; //运行正确x=undefined; //运行错误y=undefined; //运行正确z=undefined; //运行正确     x=null; //运行错误y=null; //运行错误z=null; //运行正确      x=y;//运行错误x=z;//运行错误y=x;//运行正确y=z;//运行错误z=x;//运行正确z=y;//运行正确

3.2.9 void类型

  • void表示没有任何类型

    • 函数没有返回值时,返回值类型是void

      function hello():void{ 
      alert(“Hello Angular”); 
      }

    • 对于可以忽略返回值的回调函数来说,使用void类型比任意值类型更完全一些

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

3.2.10 never类型

  • never是其它类型(包括nullundefined)的子类型,代表从不会出现的值

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

3.3声明和解构

  • 在TypeScript中,支持varletconst这样的声明方式

3.3.1 let声明

  • letvar声明变量的写法类似:

    let hello="Hello Angular";
  • let声明的变量只在块级作用域内有效:

    function f(input: boolean) {  let a=100;  if(input) {    let b=a+1;//运行正确    return b;  }  return b;//错误,b没有被定义}
  • 块级作用域不能在它声明之前被读取或赋值

    a++;//错误,在声明之前使用是不合法的let a;
  • let不允许被重复声明

    var x=2;console.log(x+3);//输出:5var x=3;console.log(x+3);//输出:6let y=2;let y=3;//报错,let声明的变量不能在一个作用域里多次声明
  • let声明在下面两种函数入参的对比:

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

3.3.2 const声明

  • const声明与let声明相似,但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--;    //正确

3.3.3 解构

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

  • 数组解构

    let input=[1,2];let [first,second]=input;console.log(first); //相当于 input[0]: 1console.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);//输出1console.log(rest);//输出:[2,3,4]
  • 对象解构

    let test={x:0,y:0,width:15,height:20};let {x,y,width,height}=test;console.log(x,y,width,height);//输出: 0,10,15,20

3.4 函数

3.4.1 函数定义

  • 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;}

3.4.2 可选参数

  • TypeScript中,被调函数的每个参数都是必传的

    function max(x:number,y:number):number {  return x>y?x:y;}let result1=max(2);//报错let result2=max(2,4,7);//报错let result3=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,7);//报错let result3=max(2,4);//运行正常

3.4.3 默认参数

  • Typescript还支持初始化默认参数,如果没有给这个参数传值或者传的值为undefined时,这个参数的值就是设置的默认值:

    function max(x:number,y=4):number {    return x>y?x:y;}let result1=max(2);//运行正常let result2=max(2,undefined);//运行正常let result3=max(2,4,7);//报错let result4=max(2,4);//运行正常
  • 如果默认值参数放到了必选参数的前面,用户必须显示地传入undefined

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

3.4.4 剩余参数

  • 当需要同时操作多个参数,或者并不知道会有多少参数传递进来时,就需要用到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

3.4.5 函数重载

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

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

3.4.6 箭头函数

let gift={  gifts:["teddy bear","spiderman","dinosaur","Thomas loco","toy ricks","Transformers"],  giftPicker:function () {    return function () {      let pickedNumber=Math.floor(Math.random()*6);      return this.gifts[pickedNumber];    }  }}let pickGift=gift.giftPicker();console.log("you get a :"+pickGift());//Uncaught TypeError: Cannot read property '1' of undefined
  • 上面giftPicker()函数里的this被设置成了window而不是gift对象,因为这里没有对this进行动态绑定,因此this就指向了window对象。

    let gift={  gifts:["teddy bear","spiderman","dinosaur","Thomas loco","toy ricks","Transformers"],  giftPicker:function () {    return ()=> {      let pickedNumber=Math.floor(Math.random()*6);      return this.gifts[pickedNumber];    }  }}let pickGift=gift.giftPicker();console.log("you get a :"+pickGift());
  • 上面代码TypeScript提供的箭头函数(=>)很好地解决了这个问题,它在函数创建时就绑定了this

3.5 类

3.5.1 类的例子

  • 传统的javascript程序使用函数和基于原型继承来创建可重用的”类”,这对于习惯了面向对象编程的开发者来说不是很友好。TypeScript中可以支持使用基于类的面向对象编程。

  • 看下面例子:

    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);  }}
    • 上面声明一个汽车类Car,这个类有三个类成员:

      • 类属性engine
      • 构造函数
      • drive()方法
    • 其中类属性engine,可通过this.engine访问

    • 实例化一个car新对象,并执行构造函数初始化:

      let car =new Car("petrol");car.drive(100);//输出:A car runs 100m powered by petrol

3.5.2 继承与多态

  • 封装继承多态是面向对象的三大特性。
  • 上面的例子中把汽车的行为写到一个类中,即所谓的封装。
  • 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() 方法
    • MotoCarJeep是基类Car的子类,通过extends来继承父类
    • 子类可以访问父类的属性和方法,也可以重写父类的方法
    • Jeepdrive()方法重写了Cardrive()方法,这样的drive()方法在不同的类中具有不同的功能,这就是多态
    • 即使landRover被声明为Car类型,它依然是子类Jeep,landRover.drive(200)调用的是Jeep里的重写方法
    • 派生类构造函数须调用super(),它会执行基类的构造方法

3.5.3 修饰符

  • 类中的修饰符三种类型:

    • 公共(public)
    • 私有(private)
    • 受保护(protected)
  • public修饰符

    • TypeScript里,每个成员默认为`public`,可以被自由地访问:

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

    • 当类成员被标记成private时,就不能在类的外部访问它,示例代码如下:

      class  Car{  private engine:string;  constructor(engine:string){    this.engine=engine;  }}new Car("petrol").engine;//报错:engine属性是私有的,只能在类内部访问
    • ES6并没有提供对私有属性的语法支持,但是可以通过闭包来实现私有属性
  • 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 electricityconsole.log(tesla.engine); //报错
    • 注意,由于engine被声明为protected,所以不能在外部访问它,但是仍然可以通过它的继承类MotoCar来访问

3.5.4 参数属性

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

    class  Car{  constructor(protected engine:string){}  drive(distanceInMeters:number=0){    console.log(`A car runs ${distanceInMeters}m powered by` +this.engine);  }}
    • 在构造函数里通过protected engine:string来创建和初始化engine成员属性,从而把声明和赋值合并到一处。

3.5.5 静态属性

  • 类的静态成员存在于类本身而不是类的实例上

    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*xDist+yDist*yDist)/this.scale;  }}let grid1= new Grid(1.0);let grid2= new Grid(5.0);console.log(grid1.calculateDistanceFromOrigin({x:10,y:10})); //输出: 14.142135623730951console.log(grid2.calculateDistanceFromOrigin({x:10,y:10})); //输出: 2.8284271247461903

3.5.6抽象类

  • 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();

3.6 模块

3.6.1 概述

  • 模块在自身的作用域里执行,而不是在全局作用域里,这意味着定义在一个模块里的变量、函数和类等,在模块外是不可见的,除非明确地使用export导出它们。反之,则必须通过import导入它们。
  • Angular中,常见的模块加载器有SystenJSWebpack

3.6.2 模块导出方式

  • 导出声明

    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";

3.6.3 模块导入方式

  • 模块导入与模块导出相对应,可以使用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();

3.6.4 模块的默认导出

  • 模块可以用default关键字实现默认导出的功能,每个模块可以有一个默认导出。

    • 默认导出类

      //ErpIdentityValidate.tsexport default class ErpIdentityValidate implements IdentityValidate {    isGfStaff(erp:string){        return erpService.contains(erp);    }}//test.jsimport validator from "./ErpIdentityValidate";let erp = new validator();
    • 默认导出函数

      // nameServiceValidate.tsexport default function (s: string) {    return nameService.contains(s);}//test.jsimport validator from "./nameValidate";let name = "Angular";//使用导出函数console.log(`"${nmae}" ${ validate(name) ? "matches" : " does not match"}`);
    • 默认导出值

      // constantService.tsexport default "Angular";//test.tsimport name from "./constantService";console.log(name);//"Angular"

3.6.4 模块设计原则

  • 尽可能的在顶层导出

    //ClassTest.tsexport default class ClassTest {  // ...}//FuncTest.tsexport default function FuncTest() {  // ...}//Test.tsimport ClassTest from "./ClassTest";import FuncTest from "./FuncTest";let C=new ClassTest();FuncTest();
    • 返回多个对象的时候,可以采用顶层导出的方式,调用的时候再明确地列出导入的对象名称即可:

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

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

        import { ClassTest,FunctTest} from "./ModuleTest";
    • 使用命名空间模式导出

      // MyLargeModule.tsexport class Dog {/* ... */ }export class Cat {/* ... */ }export class Tree {/* ... */ }export class Flower {/* ... */ }//Consumer.tsimport* as myLargeModule from "./MyLargeModule.ts";let x= new myLargeModule.Dog();
    • 使用模块包装进行扩展

      • 我们可能经常要去扩展一个模块的功能,推荐的方案是不要去改变原来的对象,而是导出一个新的对象来提供新的功能,示例代码如下:

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

3.7接口

3.7.1 概述

  • TypeScript接口的使用方式类似于Java

3.7.2 属性类型接口

  • 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);
    • 传给printLable()方法的对象只要”形式上”满足接口的需求即可
    • 接口类型检查器不会去检查属性顺序,但要确保相应的属性存在且类型匹配
  • 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

3.7.3 函数类型接口

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

    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");
    • 函数类型的接口需要注意: 
      • 使用时参数个数需与接口定义的参数相同,对应位置变量的数据类型需保持一致,参数名可以不一样
      • 函数的额返回值:函数的返回值类型与接口定义的返回值类型要一致。

3.7.4 可索引类型接口

  • 通过特定的索引来得到指定类型的返回值:

    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"]);  //输出: 张三

3.7.5 类类型接口

  • 类类型接口用来规范一个类的内容,示例代码如下:

    interface Animal {  name: string;}class Dog implements Animal {    name: string;    constructor(n: string) {}}
    • 我们可以在接口中描述一个方法,并在类里去具体实现它的功能,如同下面的setName方法一样:

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

3.7.6 接口扩展

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

    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();

3.8 装饰器

3.8.1 概述

  • 装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上。
  • 装饰器由@符号紧跟一个函数名称
  • 装饰器是用来给附着的主体进行装饰,添加额外的行为

    //方法装饰器declare type MethodDecorarot =<T>(target: Object,propertyKey: string | symbol,    descriptor: TypedPropertyDescriptor<T>)=> TypedPropertyDescriptor<T> | void;//类装饰器declare type ClassDecorator =<TFunction extends Function>(target: TFunction) => TFunction | void;//参数装饰器declare type ParameterDecorator = ( target: Object, propertyKey: string | symbol,parameterIndex: number) => void;//属性装饰器declare type PropertyDecorator = ( target: Object, propertyKey: string | symbol) => void;

3.8.2 方法装饰器

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

    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?: ()=>; //属性的访问器函数(getter)    set?: (value: T) => void;//属性器的设置器函数(setter)}
  • 方法装饰器的例子:

    class TestClass {    @log    testMethod(arg: string) {        return "logMsg: " +arg;    }}
    • 下面是方法装饰器@log的实现:

      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 被调用时,它会打印日志信息。

3.8.3 类装饰器

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

    declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
    • 如上所示,类的构造函数作为其唯一的参数。类装饰器在运行时会被当作函数的形式来调用。
    • 假如类装饰器返回了一个值,那么它会在构造函数中替换类的声明。下面是使用类装饰器(@Component)的例子,应用到Person类,示例代码如下:

      @Component({    selector: 'person',    template: 'person.html'})class Person { constructor(    public firstNmae: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;         retuen f;  //返回构造函数}
      • 然后可以使用以下代码进行测试:

        let p = new Person("Angular","JS");
      • 结果输出如下:

        selector:persontemplate:person.htmlPerson: Person(["Angular","JS"])
      • 上面代码看起来有点繁琐,因为我们返回了一个新的构造函数,必须自己处理好原来的原型链。

3.8.4 参数装饰器

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

    declare type ParameterDecorator =(target: Object,propertyKey: string | symbol,parameterIndex: number) => void;
  • 如上所述,包含3个参数:

    • target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    • propertyKey: 参数名称。
    • parameterIndex: 参数在函数参数列表中的索引。
  • 下面是参数装饰器的一个简单例子:

    class userService {    login(@inject name: string) {}}// @inject 装饰器的实现function inject (target: any,propertyKet: string | symbol,parameterIndex: number) {    console.log(target); //userService prototype    console.log(propertyKey);     console.log(parameterIndex); } // 结果输出如下:Ojectlogin0

3.8.5 属性装饰器

  • 属性装饰器

    • 属性装饰器的定义如下:

      declare type PropertyDecorator =(target: Object,propertyKey: string | symbol) => void;
    • 如上所述,包含2个参数:

      • target: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
      • propertyKey: 属性名字

3.8.6 装饰器组合

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

    • 从左到右的书写:

      @decoratorA @decoratorB param
    • 从上到下的书写:

      @decoratorA@decoratorBfunctionA
  • TypeScript里,当多个装饰器应用在同一个声明上的时候,会进行如下步骤的操作:

    • 从左到右(从上到下)依次执行装饰器函数,得到返回结果
    • 返回结果会被当做函数,从左到右(从上到下)依次调用
  • 下面是两个类装饰器复合应用的例子,注意看输出结果所显示的执行顺序,示例代码如下:

    function Component (component) {    console.log('selector: '+component.selector);    console.log('template: '+component.tempalte);    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: undefined component init      directive init      directive call      component call      

3.9 泛型

  • 在实际开发中,我们定义的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('c');                                                                                                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'])); 

3.10 TypeScript周边

3.10.1 编译配置文件

  • 通过配置文件能减少繁琐的命令行参数,提升开发效率

3.10.2 声明文件

0 0
原创粉丝点击