1. 程式人生 > >揭祕Angular 2(一):TypeScript入門

揭祕Angular 2(一):TypeScript入門

3.1 TypeScript概述

3.1.1概述

3.1.2安裝

  • 檢查npm是否已經安裝
  • 接下來我們使用2.0正式版的TypeScript,安裝命令如下:

    npm install -g [email protected]
    
  • 寫第一個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,可以用|來支援多種型別

      //啟用 --stricNullChecks
      let x:number;
      let y:number | undefined
      let z:number | null | undefined
      
      x=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);//輸出:5
    var x=3;
    console.log(x+3);//輸出:6
    
    let 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);//返回0
    funB(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]: 1
    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: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 electricity
    
    console.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.142135623730951
    console.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.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"
      

3.6.4 模組設計原則

  • 儘可能的在頂層匯出

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

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:st