国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

從 JavaScript 到 TypeScript - 泛型

2018-08-28 20:34 更新

TypeScript 為 JavaScriopt 帶來(lái)了強(qiáng)類型特性,這就意味著限制了類型的自由度。同一段程序,為了適應(yīng)不同的類型,就可能需要寫(xiě)不同的處理函數(shù)——而且這些處理函數(shù)中所有邏輯完全相同,唯一不同的就是類型——這嚴(yán)重違反抽象和復(fù)用代碼的原則。

一個(gè)小實(shí)例

我們來(lái)模擬一個(gè)場(chǎng)景:某個(gè)服務(wù)提供了一些不同類型的數(shù)據(jù),我們需要先通過(guò)一個(gè)中間件對(duì)這些數(shù)據(jù)進(jìn)行一個(gè)基本的處理(比如驗(yàn)證,容錯(cuò)等),再對(duì)其進(jìn)行使用。那么用 JavaScript 來(lái)寫(xiě)應(yīng)該是這樣的

JavaScript 源碼

// 模擬服務(wù),提供不同的數(shù)據(jù)。這里模擬了一個(gè)字符串和一個(gè)數(shù)值
var service = {
    getStringValue: function() {
        return "a string value";
    },
    getNumberValue: function() {
        return 20;
    }
};


// 處理數(shù)據(jù)的中間件。這里用 log 來(lái)模擬處理,直接返回?cái)?shù)據(jù)當(dāng)作處理后的數(shù)據(jù)
function middleware(value) {
    console.log(value);
    return value;
}


// JS 中對(duì)于類型并不關(guān)心,所以這里沒(méi)什么問(wèn)題
var sValue = middleware(service.getStringValue());
var nValue = middleware(service.getNumberValue());

改寫(xiě)成 TypeScript

先來(lái)看看對(duì)服務(wù)的改寫(xiě),TypeScript 版的服務(wù)有返回類型:

const service = {
    getStringValue(): string {
        return "a string value";
    },


    getNumberValue(): number {
        return 20;
    }
};

為了保證在對(duì) sValuenValue 的后續(xù)操作中類型檢查有效,它們也會(huì)有類型(如果 middleware 類型定義得當(dāng),可以推導(dǎo),這里我們先顯示定義其類型)

const sValue: string = middleware(service.getStringValue());
const nValue: number = middleware(service.getNumberValue());

現(xiàn)在的問(wèn)題是 middleware 要怎么樣定義才既可能返回 string,又可能返回 number,而且還能被類型檢查正確推導(dǎo)出來(lái)?

第 1 個(gè)辦法,用 any

function middleware(value: any): any {
    console.log(value);
    return value;
}

是的,這個(gè)辦法可以檢查通過(guò)。但它的問(wèn)題在于 middleware 內(nèi)部失去了類型檢查,在后在對(duì) sValuenValue 賦值的時(shí)候,也只是當(dāng)作類型沒(méi)有問(wèn)題。簡(jiǎn)單的說(shuō),是有“假裝”沒(méi)問(wèn)題。

第 2 個(gè)辦法,多個(gè) middleware

function middleware1(value: string): string { ... }
function middleware2(value: number): number { ... }

當(dāng)然也可以用 TypeScript 的重載(overload)來(lái)實(shí)現(xiàn)

function middleware(value: string): string;
function middleware(value: number): number;
function middleware(value: any): any {
    // 實(shí)現(xiàn)一樣沒(méi)有嚴(yán)格的類型檢查
}

這種方法最主要的一個(gè)問(wèn)題是……如果我有 10 種類型的數(shù)據(jù),就需要定義 10 個(gè)函數(shù)(或重載),那 20 個(gè),200 個(gè)呢……

正解:使用泛型(Generic)

現(xiàn)在我們切入正題,用泛型來(lái)解決這個(gè)問(wèn)題。那么這就需要解釋一下什么是泛型了:泛型就是指定一個(gè)表示類型的變量,用它來(lái)代替某個(gè)實(shí)際的類型用于編程,而后通過(guò)實(shí)際調(diào)用時(shí)傳入或推導(dǎo)的類型來(lái)對(duì)其進(jìn)行替換,以達(dá)到一段使用泛型程序可以實(shí)際適應(yīng)不同類型的目的。

雖然這個(gè)解釋已經(jīng)很接地氣了,但是理解起來(lái)還是不如一個(gè)實(shí)例來(lái)得容易。我們來(lái)看看 middleware 的泛型實(shí)現(xiàn)是怎么樣的

function middleware<T>(value: T): T {
    console.log(value);
    return value;
}

middleware 后面緊接的 <T> 表示聲明一個(gè)表示類型的變量,Value: T 表示聲明參數(shù)是 T 類型的,后面的 : T 表示返回值也是 T 類型的。那么在調(diào)用 middlewre(getStringValue()) 的時(shí)候,由于參數(shù)推導(dǎo)出來(lái)是 string 類型,所以這個(gè)時(shí)候 T 代表了 string,因此此時(shí) middleware 的返回類型也就是 string;而對(duì)于 middleware(getNumberValue()) 調(diào)用來(lái)說(shuō),這里的 T 表示了 number。

我們直接從 VSCode 的提示可以看出來(lái),對(duì)于 middleware<T>() 調(diào)用,TypeScript 可以推導(dǎo)出參數(shù)類型和返回值類型:

clipboard.png

我們也可以在調(diào)用的時(shí)候,小括號(hào)前顯示指定 T 代替的類型,比如 mdiddleware<string>(...),不過(guò)如果指定的類型與推導(dǎo)的類型有沖突,就會(huì)提示錯(cuò)誤:

clipboard.png

泛型類

前面已經(jīng)解釋了“泛型”這個(gè)概念。示例中泛型的用法我們稱之為“泛型函數(shù)”。不過(guò)泛型更廣泛的用法是用于“泛型類”——即在聲明類的時(shí)候聲明泛型,那么在類的整個(gè)個(gè)作用域范圍內(nèi)都可以使用聲明的泛型類型。

相信大家都已經(jīng)對(duì)數(shù)組有所了解,比如 string[] 表示字符串?dāng)?shù)組類型。其實(shí)在早期的 TypeScript 版本中沒(méi)有這種數(shù)組類型表示,而是采用實(shí)例化的泛型 Array<string> 來(lái)表示的,現(xiàn)在仍然可以使用這方式來(lái)表示數(shù)組。

除此之外,TypeScript 中還有一個(gè)很常用的泛型類,Promise<T>。因?yàn)?Promise 往往是帶數(shù)據(jù)的,所以通過(guò) Promise<T> 這種泛型定義的形式,可以表示一個(gè) Promise 所帶數(shù)據(jù)的類型。比如下圖就可以看出,TypeScript 能正確推導(dǎo)出 n 的類型是 number

clipboard.png

所以,泛型類其實(shí)多數(shù)時(shí)候是應(yīng)用于容器類。假設(shè)我們需要實(shí)現(xiàn)一個(gè) FilteredList,我們可以向其中 add()(添加) 任意數(shù)據(jù),但是它在添加的時(shí)候會(huì)自動(dòng)過(guò)濾掉不符合條件的一些,最終通過(guò) get all() 輸出所有符合條件的數(shù)據(jù)(數(shù)組)。而過(guò)濾條件在構(gòu)造對(duì)象的時(shí)候,以函數(shù)或 Lambda 表達(dá)式提供。

// 聲明泛型類,類型變量為 T
class FilteredList<T> {
    // 聲明過(guò)濾器是以 T 為參數(shù)類型,返回 boolean 的函數(shù)表達(dá)式
    filter: (v: T) => boolean;
    // 聲明數(shù)據(jù)是 T 數(shù)組類型
    data: T[];
    constructor(filter: (v: T) => boolean) {
        this.filter = filter;
    }


    add(value: T) {
        if (this.filter(value)) {
            this.data.push(value);
        }
    }


    get all(): T[] {
        return this.data;
    }
}


// 處理 string 類型的 FilteredList
const validStrings = new FilteredList<string>(s => !s);


// 處理 number 類型的 FilteredList
const positiveNumber  = new FilteredList<number>(n => n > 0);

甚至還可以把 (v: T) => boolean 聲明為一個(gè)類型,以便復(fù)用

type Predicate<T> = (v: T) => boolean;


class FilteredList<T> {
    filter: Predicate<T>;
    data: T[];
    constructor(filter: Predicate<T>) { ... }
    add(value: T) { ... }
    get all(): T[] { ... }
}

當(dāng)然類型變量也不一定非得叫 T,也可以叫 TValue 或別的什么,但是一般建議以大寫(xiě)的 T 作為前綴,采用 Pascal 命名規(guī)則,方便識(shí)別。還有一些常見(jiàn)的指代,比如 TKey 表示鍵類型,TValue 表示值類型等(常用于映射表這類容器定義)。

泛型約束

有了泛型之后,一個(gè)函數(shù)或容器類能處理的類型一下子擴(kuò)到了無(wú)限大,似乎有點(diǎn)失控的感覺(jué)。所以這里又產(chǎn)生了一個(gè)約束的概念。我們可以聲明對(duì)類型參數(shù)進(jìn)行約束。

比如,我們有 IAnimal 這樣一個(gè)接口,然后寫(xiě)一個(gè) run 工具函數(shù),它可以讓動(dòng)物跑起來(lái),而且它會(huì)返回這個(gè)動(dòng)物實(shí)例本身(以便鏈?zhǔn)秸{(diào)用)。先來(lái)定義類型

interface IAnimal {
    run(): void;
}


class Dog implements IAnimal {
    run(): void {
        console.log("Dog is running");
    }
}

第 1 種 run 定義,使用接口或基類類型

function run(animal: IAnimal): IAnimal {
    animal.run();
    return animal;
}


const dog = run(new Dog());    // dog: IAnimal

這種定義的缺點(diǎn)是 dog 被推導(dǎo)成 IAnimal 類型,當(dāng)然可以通過(guò)強(qiáng)制聲明為 const dog: Dog 來(lái)指定其類型,但是誰(shuí)知道 run() 返回的是 Dog 而不是 Cat 呢。

第 2 種 run 定義,使用泛型(無(wú)約束)

function run<TAnimal>(animal: TAnimal): TAnimal {
    animal.run();   // 'run' does not exist on type 'TAnimal'
    return animal;
}

采用這種定義,dog 可以推導(dǎo)正確。不過(guò)由于 TAnimal 在這里只是個(gè)變量,可以代表任意類型,所以它并不能保證擁有 run() 方法可供調(diào)用。

第 3 種 run 定義,使用泛型約束

正解是使用泛型約束,將 TAnimal 約束為實(shí)現(xiàn)了 IAnimal。這需要在定義類型變量的使用使用 extends 來(lái)約束:

function run<TAnimal extends IAnimal>(animal: TAnimal): TAnimal {
    animal.run();   // it's ok
    return animal;
}

注意這里的語(yǔ)法,<TAnimal extends IAnimal>,雖然 IAnimal 是個(gè)接口,但這里不是在實(shí)現(xiàn)接口,extends 表示約束關(guān)系,而非繼承。它表示 extends 左邊的類型變量實(shí)現(xiàn)了右邊的類型,或者是右邊類型的子孫類,或者就是右邊的那個(gè)類型。簡(jiǎn)單的說(shuō),就是左邊類型的實(shí)例可以賦值給右邊類型的變量。

約束為類型

有時(shí)候我們希望傳入某個(gè)工具方法的參數(shù)是一個(gè)類型,這樣就可以通過(guò) new 來(lái)生成對(duì)象。這在 TypeScript 中通常是使用構(gòu)造函數(shù)來(lái)約束的,比如

function create<T extends IAnimal>(type: { new(): T }) {
    return new type();
}


const dog = create(Dog);

這里約束了 create 可以創(chuàng)建動(dòng)物的實(shí)例。如果不加 extends IAnimal,那么這個(gè) create 可以創(chuàng)建任何類型的實(shí)例。

多個(gè)類型變量

在使用泛型的時(shí)候,當(dāng)然不會(huì)限制只使用一個(gè)類型變量,我們可以使用多個(gè),比如可以這樣定義一個(gè) Pair

class Pair<TKey, TValue> {
    private _key: TKey;
    private _value: TValue;
    constructor(key: TKey, value: TValue) {
        this._key = key;
        this._value = value;
    }


    get key() { return this._key; }
    get value() { return this._value; }
}

其它應(yīng)用

自己定義泛型結(jié)構(gòu)(泛型類或泛型函數(shù))通常只會(huì)在寫(xiě)比較復(fù)雜的應(yīng)用時(shí)發(fā)生。但是使用已定義好的泛型是極其常見(jiàn)的,上面已經(jīng)提到了兩個(gè)常見(jiàn)的泛型定義,T[]/Array<T>Promise<T>,除此之外,還有 ES6 的 SetMap 對(duì)應(yīng)于 TypeScript 的泛型定義 Set<T>Map<TK, TV>。另外,泛型還常用于 Generator 和 Iterable/Iterator:

// 產(chǎn)生 n 個(gè)隨機(jī)整數(shù)
function* randomInt(n): Iterable<number> {
    for (let i = 0; i < n; i++) {
        yield ~~(Math.random() * Number.MAX_SAFE_INTEGER);
    }
}


for (let n of randomInt(10)) {
    console.log(n);
}

擴(kuò)展閱讀

此文首發(fā)于 SegmentFault

敬請(qǐng) 掃碼 關(guān)注〔邊城〕的公眾號(hào):邊城客棧

公眾號(hào)“邊城客?!? /></p></div>
				          <div style=

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)