Learning TypeScript

kunkuntang發表於2019-02-21

使用函式

在TypeScript中使用函式

函式宣告和函式表示式

在JavaScript中有兩種宣告函式形式:命名函式和匿名函式。對於匿名函式來說,也有我們可以把它賦值給一個變數,從而變得與命名函式的表現一致。

function namedFunc() {} //命名函式
var unnamedFunc = function() {} // 把一個匿名函式賦值給一個變數
複製程式碼

它們兩的不同之處在於變數提升

函式型別

function greetNamed(name : string) : string {
    if (name) {
        return 'Hi!' + name;
    }
}
複製程式碼

宣告瞭一個引數為字串的,返回值為字串的命名函式。

先宣告一個變數,再把一個函式賦值給這個變數:

var greeetUnnamed : (name : string) => string;

greeetUnnamed = function(name : string) : string {
    if (name) {
        return 'Hi!' + name;
    }
}
複製程式碼

合成一行的寫法:

var greeetUnnamed : (name : string) => string = function(name : string) : string {
    if (name) {
        return 'Hi!' + name;
    }
}
複製程式碼

有可選引數的函式

function add(foo : number, bar : number, foobar? : number) : number {
    var result = foo + bar;
    if(foobar !== undefined) {
        result += foobar;
    }
    return result;
}
複製程式碼

引數foobar的名稱後面跟著一個?,說明了這個引數不是必填的。當我們提供兩個或三個引數時編譯器不會報錯。

add() // 報錯
add(2, 2) // 4
add(2, 2, 2) // 6
複製程式碼

有預設引數的函式

function add(foo : number, bar : number, foobar : number = 0) : number {
    var result = foo + bar;
    if(foobar !== undefined) {
        result += foobar;
    }
    return result;
}
複製程式碼

引數foobar在宣告型別後面使用=操作符並提供一個預設值,即可指定這個引數是可選的,並且在foobar沒有傳給函式時設定一個預設值。

有剩餘引數的函式

function add(...foo : number[]) : number {
    var result = 0;
    for(var i = 0; i < foo.length; i++) {
        result += foo[i]
    }
    return result;
}
複製程式碼

上面程式碼使用foo引數代替了之前的foo、bar和foobar,並且foo引數前面有三個點的省略號。一個剩餘引數必須包含一個陣列型別,否則會出現編譯錯誤。現在可以以任意數量呼叫add函式。

add() // 0
add(2) // 2
add(2, 2) // 4
add(2, 2, 2) // 6
複製程式碼

雖然沒有具體的引數數量限制,天蠍座上可以取數字型別的最大值。但實際上,這依賴於如何呼叫這個函式。

函式過載

函式過載或方法是使用相同名字和不同引數或型別建立多個方法的一種能力。

要實現函式過載

  • 先要宣告所有過載方法的定義,注意不包含方法的實現
  • 再宣告一個引數為any型別或聯合型別的實現方法。
  • 實現實現方法並通過引數型別(和返回型別)不同來實現過載。
// 先要宣告所有過載方法的定義
function test(name: string): string; // 過載方法
function test(age: number): string; // 過載方法
function test(single: boolean): string; // 過載方法
// 宣告一個引數為any型別或聯合型別的實現方法
function test(value: (string | number | boolean): string { // 實現方法
    switch(typeof value) {
        case 'string': return `My name is ${vallue}`;
        case 'number': return `I am ${vallue} years old`;
        case 'boolean': return value ? "I am single." : "I am not single.";
    }
}
複製程式碼

實現方法必須在所有過載方法的最後面

範型

先看下面例子:

class User {
    name: string;
    age: number;
}

function getUsers(cb: (users: User[]) => void): void {
    $.ajax({
        url: '/api/users',
        method: 'GET',
        success: function(data) {
            cb(data.items);
        },
        error: function(error) {
            cb(null);
        }
    })
}

class Order {
    id: number;
    total: number;
    items: any[];
}
function getOrders(cb: (orders: Order[]) => void): void {
    $.ajax({
        url: '/api/orders',
        method: 'GET',
        success: function(data) {
            cb(data.items);
        },
        error: function(error) {
            cb(null);
        }
    })
}
複製程式碼

上面程式碼中getOrders函式和getUsers函式幾乎完全相同,其中不同的只有請求url和傳入cb的引數。

面對以上場景我們可以使用泛型來避免程式碼重複。

class User {
    name: string;
    age: number;
}

class Order {
    id: number;
    total: number;
    items: any[];
}

function getEntities<T>(url: string, cb: (list: T[]) => void): void {
    $.ajax({
        url: url,
        method: 'GET',
        success: function(data) {
            cb(data.items);
        },
        error: function(error) {
            cb(null);
        }
    })
}
複製程式碼

在函式名後面增加一對括號(< >)來表明這是一個範型函式。如果角括號內是一個字元T,則它表示一種型別。函式的第一個引數是字串型別的url,第二個引數是函式型別的cb,它接受一個T型別的引數作為唯一的引數。我們可以這樣使用函式:

function getEntities<User>('/api/users', function(users: User[]) {
    for(var i = 0; users.length; i++) {
        console.log(users[i].name);
    }
}

function getEntities<Order>('/api/orders', function(orders: Order[]) {
    for(var i = 0; orders.length; i++) {
        console.log(orders[i].total);
    }
}
複製程式碼

TypeScript中的非同步程式設計

回撥和高階函式

在TypeScript中,函式可以作為引數傳給其他函式。被傳遞給其他函式的函式叫作回撥。函式也可以被另一個函式返回。那些接受函式為引數(回撥)或返回另一個函式的函式被稱為高階函式。回撥通常被用在非同步函式中:

// 回撥函式
var foo = function() { 
    console.log(' foo');
}
//高階函式
function bar(cb : () => void) {
    console.log('bar') ;
    cb() ;
}
//高階函式
function bar2() : function {
    return function() {
        console.log('bar2')
    }
}
bar(foo); //輸出'bar' 然後輸出'foo'箭頭函式
複製程式碼

箭頭函式

在TypeScript 中,我們可以使用function表示式或者箭頭函式定義一個函式。箭頭函式是function表示式的縮寫,並且這種詞法會在其作用域內繫結this操作符。

在TypeScript中, this操作符的行為和其他語言有一點不一樣。當在TypeScript中定義一個類的時候,可以使用this指向這個類自身的屬性。讓我們看一個例子:

class Person {
    name : string;
    constructor (name : string) {
        this.name = name;
    }
    greet() {
        alert(`Hi! My name is ${this. name}`);
    }
}
var remo = new Person ("Remo") ;
remo.greet() ; // "Hi! My name is Remo"
複製程式碼

我們定義了一個Person類,幷包含了一個名為name的字串型別的屬性。這個類有一個建構函式和一個叫做greet的方法。我們可以新建一個名為remo的例項,並且呼叫greet方法,它在內部使用this操作符訪問remo的name的屬性。在greet方法內部,this操作符指向裝載了greet方法的物件。

我們必須謹慎使用this操作符,因為在一些場景下它將指向錯誤的值。我們在上一個例子中加入一個方法:

class Person {
    name : string;
    constructor (name : string) {
        this.name = name;
    }
    greet() {
        alert(`Hi! My name is ${this. name}`);
    }
    greetDelay(time: number) {
        setTimeout(function() {
            alert(`Hi! My name is ${this. name}`);
        }, time);
    }
}
var remo = new Person ("Remo") ;
remo.greet() ; // "Hi! My name is Remo"
remo.greetDelay(1000) ; // "Hi! My name is"
複製程式碼

在greetDelay方法中,我們實現了一個和greet方法幾乎相同的功能。但這次這個方法接受一個名為time的引數,它用來延遲問候資訊的發出。

為了延遲問候訊息,我們使用了setTimeout函式和一個回撥函式。當定義了一個非同步函式時(包含回撥), this關鍵字就會改變它指向的值,指向匿名函式。這就解釋了為什麼remo沒有通過greetDelay方法顯示出來。

提醒下,箭頭函式表示式的詞法會繫結this操作符。這就意味著,我們可以增加函式而不用擔心this的指向。現在將上面例子中的function 表示式替換成箭頭函式:

class Person {
    name : string;
    constructor (name : string) {
        this.name = name;
    }
    greet() {
        alert(`Hi! My name is ${this. name}`);
    }
    greetDelay(time: number) {
        setTimeout(() => {
            alert(`Hi! My name is ${this. name}`);
        }, time);
    }
}
var remo = new Person ("Remo") ;
remo.greet() ; // "Hi! My name is Remo"
remo.greetDelay(1000) ; // "Hi! My name is Remo"
複製程式碼

通過使用箭頭函式,我們可以保證this操作符指向的是Person的例項而不是setTimeout的回撥函式。如果我們執行greetDelay方法,name屬性會按預期正常顯示。

生成器(generator)

一個生成器代表了一個值的序列。生成器物件的介面只是一個迭代器,可以呼叫next()函式使它產出結果。

可以使用function關鍵字後面跟一個星號(*)定義一個生成器的建構函式。yield關鍵字被用來暫停函式的執行並返回一個值。讓我們來看一個例子:

function* foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}
var bar = new foo();
bar.next(); // Object { value: 1, done: false }
bar.next(); // Object { value: 2, done: false }
bar.next(); // Object { value: 3, done: false }
bar.next(); // Object { value: 4, done: false }
bar.next(); // Object { value: 5, done: false }
bar.next(); // Object { done: true }
複製程式碼

你可以看到,這個迭代器有5個步驟。第一次呼叫next()的時候,函式會執行到第一個yield的位置,並且它會返回值1並且停止執行,直到next()再次被呼叫。可以看到,我們現在可以終止函式的執行了。可以像下面的例子這樣編寫一個無限迴圈而不會導致棧溢位:

function* foo() {
    var i = 1;
    while (true) {
        yield i++;
    }
}
var bar = new foo();
bar.next(); // Object { value: 1, done: false }
bar.next(); // Object { value: 2, done: false }
bar.next(); // Object { value: 3, done: false }
bar.next(); // Object { value: 4, done: false }
bar.next(); // Object { value: 5, done: false }
// ...
複製程式碼

生成器給了我們以同步的方式編寫非同步程式碼的可能性,只要我們在非同步事件發生的時候呼叫生成器的next()方法就能做到這一點。

非同步函式——async和await

非同步函式是一個即將到來的TypeScript的特性。一個非同步函式是在非同步操作中被呼叫的函式。開發都可以使用await關鍵字等待非同步結果的到來而不會阻塞程式的執行。

當編譯目標是ES6時,非同步函式將會被promise實現,編譯目標是ES5和ES3時會使用promise的相容版本實現。

與使用promise相比,使用非同步函式可以顯著提高程式的可讀性,在技術上使用promise可以達到和同步同樣的效果:

var p: Promise<number> = /* ... */;
async function fn(): Promise<number> {
    var i = await p;
    return 1 + i;
}
複製程式碼

上面的程式碼片段中我們宣告瞭一個名為p的promise,這個promise將會等待被執行。在等待期間,程式並不會被阻塞,因為我們是在一個名為fn的非同步函式中等待它。可以看到,fn函式前面有一個async關鍵字,這將會對編譯器指明這是一個非同步函式。

在函式內部,await關鍵字被用來暫停程式碼執行,直到p被fulfilled。可以看到,這個語法更有語義並更加簡潔。

TypeScript中的物件導向程式設計

SOLID原則

SOLID原則通常是針對避免OOP(物件導向程式語言)開發模式中出現的錯誤所作出的規範。一共有五個:

  • 單一職責原則(SRP):表明軟體元件(函式、類、模組)必須專注於單-一的任務(只有單一的職責)。
  • 開/閉原則(OCP):表明軟體設計時必須時刻考慮到(程式碼)可能的發展(具有擴充套件性),但是程式的發展必須最少地修改已有的程式碼(對已有的修改封閉)。
  • 里氏替換原則(LSP):表明只要繼承的是同一個介面,程式裡任意一個類都可以被其他的類替換。在替換完成後,不需要其他額外的工作程式就能像原來一樣執行。
  • 介面隔離原則(ISP):表明我們應該將那些非常大的介面(大而全的介面)拆分成一些小的更具體的介面(特定客戶端介面),這樣客戶端就只需關心它們需要用到的介面。
  • 依賴反轉原則(DIP):表明一個方法應該遵從依賴於抽象(介面)而不是一個例項(類)的概念。

class Person {
  public name: string;
  public surname: string;
  public email: string;
  constructor(name: string, surname: string, email: string) {
    this.email = email;
    this.name = name;
    this.surname = surname;
  }
  greet() {
    alert("Hi!");
  }
}
var me: Person = new Person("Remo", "Jansen", "remo.jansen@wolksoftware.com");
複製程式碼

相關文章