国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從 JavaScript 到 TypeScript - 接口

darkbaby123 / 713人閱讀

摘要:前面講泛型的時候,提到了接口。和泛型一樣,接口也是目前中并不存在的語法。不過可不吃這一套,所以這里通過注釋關(guān)閉了對該接口的命名檢查。這樣的接口不能由類實(shí)現(xiàn)。

前面講 泛型 的時候,提到了接口。和泛型一樣,接口也是目前 JavaScript 中并不存在的語法。

由于泛型語法總是附加在類或函數(shù)語法中,所以從 TypeScript 轉(zhuǎn)譯成 JavaScript 之后,至少還存在類和函數(shù)(只是去掉了泛型定義,類似 Java 泛型的類型擦除)。然而,如果在某個 .ts 文件中只定義了接口,轉(zhuǎn)譯后的 .js 文件將是一個空文件——接口被完全“擦除”了。

那么,TypeScript 中為什么要出現(xiàn)接口語法?而對于沒接觸過強(qiáng)類型語法的 JSer 來說,接口到底是個什么東西?

什么是接口

現(xiàn)實(shí)生活中我們會遇到這么一個問題:出國旅游之前,往往需要了解目的地的電源插座的情況:

是什么形狀,是三插還是雙插,是平插還是圓插?

如果形狀相同,電壓多少,110V 還是 220V 或者 380V?

直流電還是交流電?

大家都知道,國內(nèi)的電源插頭常見的有兩種,三平插(比如多數(shù)筆記本電腦電源插頭)和雙平插(比如多數(shù)手機(jī)電源插頭),家用電壓都是 220V。但是近年來電子產(chǎn)品與國際接軌,電源適配器和充電器一般都支持 100~220V 電壓。

那么上面就出現(xiàn)了兩類標(biāo)準(zhǔn),一類是插座的標(biāo)準(zhǔn),另一類是插頭的標(biāo)準(zhǔn)。如果這兩類標(biāo)準(zhǔn)一樣,我們就可以提包上路,不用擔(dān)心到地方后手機(jī)充不上電,電腦找不到合適電源的問題。但是,如果標(biāo)準(zhǔn)不一樣,就必須去買個轉(zhuǎn)換插頭,甚至是帶變壓功能的轉(zhuǎn)換插頭。

這里提到的轉(zhuǎn)換插頭在軟件開發(fā)中屬于“適配器模式”,這里不深研。我們要研究的是插座和插頭的標(biāo)準(zhǔn)。插座就是留在墻上的接口,它有自身的標(biāo)準(zhǔn),而插頭為了能使用這個插座,就必須符合它的標(biāo)準(zhǔn),換句話說,得匹配接口。工業(yè)上這像插座這樣的標(biāo)準(zhǔn)必須成文、審批、公布并執(zhí)行,而編程上的接口也類似,需要定義接口、類型檢查(編譯器)、公布文檔,實(shí)現(xiàn)接口。

所以回到 TypeScript,我們以關(guān)鍵字 interface,用類似于 class 聲明的語法在定義接口 (還記得聲明類型一文中提到的類成員聲明嗎)。所以一個接口看起來可能是這樣的

interface INamedLogable {
    name: string;
    log(...args: any[]);
}
通過實(shí)例講接口

假設(shè)我們的業(yè)務(wù)中有這樣一部分 JavaScript 代碼

function doWith(logger) {
    console.log(`[Logger] ${logger.name}`);
    logger.log("begin to do");
    // ...
    logger.log("all done");
}

doWith({
    name: "jsLogger",
    log(...args) {
        console.log(...args);
    }
})
翻譯成 TypeScript

我們還不懂接口,所以先定義一個類,包含 name 屬性和 log() 方法。有了這個類就可以在 doWith() 和其它定義中使用它來進(jìn)行類型約束(檢查)。

class JsLogger {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    log(...args: any[]) {
        console.log(...args);
    }
}

然后定義 doWith

function doWith(logger: JsLogger) {
    console.log(`[Logger] ${logger.name}`);
    logger.log("begin to do");
    // ...
    logger.log("all done");
}

調(diào)用示例:

const logger = new JsLogger("jsLogger");
doWith(logger);
給 log() 方法加點(diǎn)料

上面的示例中,輸出的日志只有日志內(nèi)容本身,但是我們希望能在日志信息每行前面綴上日志名稱,比如像這樣的輸出

[jsLogger] begin to do

所以我們從 JsLogger 繼承出來一個 PoweredJsLogger 來用:

class PoweredJsLogger extends JsLogger {
    log(...args: any[]) {
        console.log(`[${this.name}]`, ...args);
    }
}

const logger = new PoweredJsLogger("jsLogger");
doWith(logger);
換個第三方 Logger

甚至我們可以換個第三方 Logger,與 JsLogger 毫無關(guān)系,但成員定義相同

function doWith(logger: JsLogger) {
    console.log(`[Logger] ${logger.name}`);
    logger.log("begin to do");
    // ...
    logger.log("all done");
}

const logger = new AnotherLogger("oops");
doWith(logger);

你以為它會報(bào)錯?沒有,它轉(zhuǎn)譯正常,運(yùn)行正常,輸出

[Logger] oops
[Another(oops)] begin to do
[Another(oops)] all done

看到這個結(jié)果,Java 和 C# 程序員要抓狂了。不過 JSer 覺得這沒什么啊,我們平時經(jīng)常這么干。

從類 (class) 聲明接口

理論上來說,接口是一個抽象概念,類是一個更具體的抽象概念——是的,類不是實(shí)體 (instance),從類產(chǎn)生的對象才是實(shí)體。一般情況下,我們的設(shè)計(jì)過程是從具體到抽象,但開發(fā)(編程)過程正好相反,是從抽象到具體。所以一般在開發(fā)過程中都是先定義接口,再定義實(shí)現(xiàn)這個接口的類。

當(dāng)然有例外,我相信多數(shù)開發(fā)者會有相反的體驗(yàn),尤其是一邊設(shè)計(jì)一邊開發(fā)的時候:先根據(jù)業(yè)務(wù)需要定義類,再從這個類抽象出接口,定義接口并聲明之前的類實(shí)現(xiàn)這個接口。如果接口元素(比如:方法)發(fā)生變化,往往也是先在類中實(shí)現(xiàn),再進(jìn)行抽象補(bǔ)充到接口定義中。這種情況下我們多么希望能直接從類生成接口……當(dāng)然有工具可以實(shí)現(xiàn)這個過程,但多數(shù)語言本身并不支持——別再問我原因,剛才已經(jīng)講過了。

不過 TypeScript 帶來了不一樣的體驗(yàn),我們可以從類聲明接口,比如這樣

interface ILogger extends JsLogger {
    // 還可以補(bǔ)充其它接口元素
}

這里定義的 ILogger 和最前面定義的 INamedLogable 具有相同的接口元素,是一樣的效果。

為什么 TypeScript 支持這種反向的定義……也許真的只是為了方便。但是對于大型應(yīng)用開發(fā)來說,這并不見得是件好事。如果以后因?yàn)槟承┰蛐枰獮?JsLogger 添加公共方法,那就悲劇了——所有實(shí)現(xiàn)了 ILogger 接口的類都得實(shí)現(xiàn)這個新加的方法。也許以后某個版本的 TypeScript 會處理這個問題,至少現(xiàn)在 Java 已經(jīng)找到辦法了,這就是 Java 8 帶來的默認(rèn)方法,而且 C# 馬上也要實(shí)現(xiàn)這一特性了 。

回到上面的問題

現(xiàn)在回到上面的問題,為什么向 doWith() 傳入 AnotherLogger 對象毫不違和,甚至連個警告都沒有。

前面我們已經(jīng)提到了“鴨子辨型法”,對于 doWith(logger: JsLogger) 來說,它需要的并不真的是 JsLogger,而是 interface extends JsLogger {}。只要傳入的這參數(shù)符合這個接口約束,方法體內(nèi)的任何語句都不會產(chǎn)生語法錯誤,語法上絕對沒有問題。因此,傳入 AnotherLogger 不會有問題,它所隱含的接口定義完全符合 ILogger 接口的定義。

然而,語義上也許會有些問題,這也是我作為一個十多年經(jīng)驗(yàn)的靜態(tài)語言使用者所不能完全理解的。有可能這是 TypeScript 為了適應(yīng)動態(tài)的 JavaScript 所做出的讓步,也有可能這是 TypeScript 特意引入的特性。我對多數(shù)動態(tài)語言和函數(shù)式語言并不了解,但我相信,這肯定不是 TypeScript 首創(chuàng)。

TypeScript 接口詳述

上面大量的內(nèi)容只是為了將大家通過 class 的定義引入到對 interface 的了解。但是接口到底該怎么定義?

常規(guī)接口

常規(guī)接口的定義和類的定義幾乎沒有區(qū)別,上面已經(jīng)存在例子,歸納起來需要注意幾點(diǎn):

使用 interface 關(guān)鍵字;

接口名稱一般按規(guī)范前綴 I

接口中不包含實(shí)現(xiàn)

不對成員變量賦初始值

沒有構(gòu)造函數(shù)

沒有方法體

而對接口的實(shí)現(xiàn)可以通過 implemnets 關(guān)鍵字,比如

class MyLogger implements INamedLogable {
    name: string;
    log(...args: any[]) {
        console.log(...args);
    }
}

這是顯式地實(shí)現(xiàn),還有隱式的。

const myLogger: INamedLogable = {
    name: "my-loader",
    log(...args: any[]) {
        console.log(...args);
    }
};

另外,在所有聲明接口類型的地方傳值或賦值,TypeScript 會通過對接口元素一一對比來對傳入的對象進(jìn)行檢查。

函數(shù)類型接口

曾經(jīng)我們定義一個函數(shù)類型,是使用 type 關(guān)鍵字,以類似 Lambda 的語法來定義。比如需要定義一個參數(shù)是 number,返回值是 string 的函數(shù)類型:

// 聲明類型
type NumberToStringFunc = (n: number) => string;

// 定義符合這個類型的 hex
const hex: NumberToStringFunc = n => n.toString(16);

現(xiàn)在可以用接口語法來定義

// tslint:disable-next-line:interface-name
interface NumberToStringFunc {
    (n: number): string;
}

const hex: NumberToStringFunc = n => n.toString(16);

這種定義方式和 Java 8 的函數(shù)式接口語法類似,而且由于它表示一個函數(shù)類型,所以一般不會前綴 I,而是后綴 Func(有參) 或者 Action(無參)。不過 TSLint 可不吃這一套,所以這里通過注釋關(guān)閉了 TSLint 對該接口的命名檢查。

這樣的接口不能由類實(shí)現(xiàn)。上例中的 hex 是直接通過一個 Lambda 實(shí)現(xiàn)的。它還可以通過函數(shù)、函數(shù)表達(dá)式來實(shí)現(xiàn)。另外,它可以擴(kuò)展為混合類型的接口。

混合類型接口

JSer 們應(yīng)該經(jīng)常會用到一種技巧,定義一個函數(shù),再為這個函數(shù)賦值某些屬性——這沒毛病,JavaScript 的函數(shù)本身就是對象,而 JavaScript 的對象可以動態(tài)修改。最常見的例子應(yīng)該就是 jQuery 和 Lodash 了。

這樣的類型在 TypeScript 中就通過混合類型接口來定義,這次直接引用官方文檔的示例:

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
接口繼承

前面我們提到可以從類聲明接口,其語法采用 extends 關(guān)鍵字,所以說成是繼承也并無不可。

另外,接口還可以繼承自其它接口,比如

interface INewLogger: ILogger {
    suplier: string;
}

接口還允許從多個接口繼承,比如上面提到的 INamedLogable 可以拆分一下

interface INamed {
    name: string;
}

interface ILogable {
    log(...args: any[]);
}

interface INamedLogable extends INamed, ILogable {}

這樣定義 INamedLogable 是不是更合理一些?

后記

不管什么語言,接口的主要目的是為了在供應(yīng)者和消費(fèi)者之前創(chuàng)建一個契約,其意義更傾向于設(shè)計(jì)而非程序本身,所以接口在各種設(shè)計(jì)模式中應(yīng)用非常廣泛。不要為了接口而接口,在設(shè)計(jì)需要的時候使用它。對復(fù)雜的應(yīng)用來說,定義一套好的接口很有必要,但是對于一些小程序來說,似乎并無必要。

相關(guān)閱讀

從 JavaScript 到 TypeScript - 模塊化和構(gòu)建

從 JavaScript 到 TypeScript - 聲明類型

從 JavaScript 到 TypeScript - 泛型

關(guān)注作者的公眾號“邊城客棧” →

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/85173.html

相關(guān)文章

  • JavaScript TypeScript - 聲明類型

    摘要:要為變量或者常量指定類型也很簡單,就是在變量常量名后面加個冒號,再指定類型即可,比如聲明函數(shù)是類型,即返回值是類型聲明參數(shù)是類型聲明是無返回值的聲明是這段代碼演示了對函數(shù)類型參數(shù)類型和變量類型地聲明。變量函數(shù)參數(shù)和返回值需要申明類型。 從 JavaScript 語法改寫為 TypeScript 語法,有兩個關(guān)鍵點(diǎn),一點(diǎn)是類成員變量(Field)需要聲明,另一點(diǎn)是要為各種東西(變量、參數(shù)...

    Flands 評論0 收藏0
  • 如何JavaScript跨越TypeScript [基礎(chǔ)進(jìn)階知識點(diǎn)]

    摘要:接口前端程序員很難理解的點(diǎn)也是一門面向?qū)ο蟮恼Z言,但是中它是基于原型實(shí)現(xiàn)的,中使用了類,這樣會更清晰的體會到面向?qū)ο筮@一說法,但是實(shí)際在中的面向?qū)ο蟾油暾@些語言一樣,通過接口和類去完整的面向?qū)ο缶幊獭? 從入門到放棄的java 初中時自學(xué)過JAVA,學(xué)了大概一個多月吧, 學(xué)了一個多月,看視頻這些,后面放棄了編程。 依稀記得,那段日子極度苦逼,我想如果當(dāng)時是學(xué)javaScrip...

    wangym 評論0 收藏0
  • 如何JavaScript跨越TypeScript [基礎(chǔ)進(jìn)階知識點(diǎn)]

    摘要:接口前端程序員很難理解的點(diǎn)也是一門面向?qū)ο蟮恼Z言,但是中它是基于原型實(shí)現(xiàn)的,中使用了類,這樣會更清晰的體會到面向?qū)ο筮@一說法,但是實(shí)際在中的面向?qū)ο蟾油暾@些語言一樣,通過接口和類去完整的面向?qū)ο缶幊獭? 從入門到放棄的java 初中時自學(xué)過JAVA,學(xué)了大概一個多月吧, 學(xué)了一個多月,看視頻這些,后面放棄了編程。 依稀記得,那段日子極度苦逼,我想如果當(dāng)時是學(xué)javaScrip...

    zhunjiee 評論0 收藏0
  • TypeScript 初識

    摘要:當(dāng)你陷在一個中大型項(xiàng)目中時應(yīng)用日趨成為常態(tài),沒有類型約束類型推斷,總有種牽一發(fā)而動全身的危機(jī)和束縛。總體而言,這些付出相對于代碼的健壯性和可維護(hù)性,都是值得的。目前主流的都為的開發(fā)提供了良好的支持,比如和。參考資料中文文檔 文章博客地址:http://pinggod.com/2016/Typescript/ TypeScript 是 JavaScript 的超集,為 JavaScrip...

    iliyaku 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<