1. 程式人生 > 其它 >xmlstreamexception 引數實體未進行宣告_TypeScript之宣告合併

xmlstreamexception 引數實體未進行宣告_TypeScript之宣告合併

技術標籤:xmlstreamexception 引數實體未進行宣告

茫茫人海中與你相遇

77ce6ccc284d4f18bcb0a78d62b07396.png

相信未來的你不會很差

來源:https://www.tslang.cn/docs/handbook/declaration-merging.html

介紹

TypeScript中有些獨特的概念可以在型別層面上描述JavaScript物件的模型。 這其中尤其獨特的一個例子是“宣告合併”的概念。 理解了這個概念,將有助於操作現有的JavaScript程式碼。 同時,也會有助於理解更多高階抽象的概念。

對本檔案來講,“宣告合併”是指編譯器將針對同一個名字的兩個獨立宣告合併為單一宣告。 合併後的宣告同時擁有原先兩個宣告的特性。 任何數量的宣告都可被合併;不侷限於兩個宣告。

基礎概念

TypeScript中的宣告會建立以下三種實體之一:名稱空間,型別或值。 建立名稱空間的宣告會新建一個名稱空間,它包含了用(.)符號來訪問時使用的名字。 建立型別的宣告是:用宣告的模型建立一個型別並繫結到給定的名字上。

最後,建立值的宣告會建立在JavaScript輸出中看到的值。

Declaration TypeNamespaceTypeValue
NamespaceXX
ClassXX
EnumXX
InterfaceX
Type AliasX
FunctionX
VariableX

理解每個宣告建立了什麼,有助於理解當宣告合併時有哪些東西被合併了。

合併介面

最簡單也最常見的宣告合併型別是介面合併。 從根本上說,合併的機制是把雙方的成員放到一個同名的接口裡。

interface Box {    height: number;    width: number;}interface Box {    scale: number;}let box: Box = {height: 5, width: 6, scale: 10};

介面的非函式的成員應該是唯一的。如果它們不是唯一的,那麼它們必須是相同的型別。如果兩個介面中同時聲明瞭同名的非函式成員且它們的型別不同,則編譯器會報錯。

對於函式成員,每個同名函式宣告都會被當成這個函式的一個過載。同時需要注意,當介面 A與後來的介面A合併時,後面的介面具有更高的優先順序。

如下例所示:

interface Cloner {    clone(animal: Animal): Animal;}interface Cloner {    clone(animal: Sheep): Sheep;}interface Cloner {    clone(animal: Dog): Dog;    clone(animal: Cat): Cat;}

這三個介面合併成一個宣告:

interface Cloner {    clone(animal: Dog): Dog;    clone(animal: Cat): Cat;    clone(animal: Sheep): Sheep;    clone(animal: Animal): Animal;}

注意每組接口裡的宣告順序保持不變,但各組介面之間的順序是後來的介面重載出現在靠前位置。

這個規則有一個例外是當出現特殊的函式簽名時。如果簽名裡有一個引數的型別是單一的字串字面量(比如,不是字串字面量的聯合型別),那麼它將會被提升到過載列表的最頂端。

比如,下面的介面會合併到一起:

interface Document {    createElement(tagName: any): Element;}interface Document {    createElement(tagName: "div"): HTMLDivElement;    createElement(tagName: "span"): HTMLSpanElement;}interface Document {    createElement(tagName: string): HTMLElement;    createElement(tagName: "canvas"): HTMLCanvasElement;}

合併後的Document將會像下面這樣:

interface Document {    createElement(tagName: "canvas"): HTMLCanvasElement;    createElement(tagName: "div"): HTMLDivElement;    createElement(tagName: "span"): HTMLSpanElement;    createElement(tagName: string): HTMLElement;    createElement(tagName: any): Element;}

合併名稱空間

與介面相似,同名的名稱空間也會合並其成員。名稱空間會創建出名稱空間和值,我們需要知道這兩者都是怎麼合併的。

對於名稱空間的合併,模組匯出的同名介面進行合併,構成單一名稱空間內含合併後的介面。

對於名稱空間裡值的合併,如果當前已經存在給定名字的名稱空間,那麼後來的名稱空間的匯出成員會被加到已經存在的那個模組裡。

Animals宣告合併示例:

namespace Animals {    export class Zebra { }}namespace Animals {    export interface Legged { numberOfLegs: number; }    export class Dog { }}

等同於:

namespace Animals {    export interface Legged { numberOfLegs: number; }    export class Zebra { }    export class Dog { }}

除了這些合併外,你還需要了解非匯出成員是如何處理的。非匯出成員僅在其原有的(合併前的)名稱空間內可見。這就是說合並之後,從其它名稱空間合併進來的成員無法訪問非匯出成員。

下例提供了更清晰的說明

namespace Animal {    let haveMuscles = true;    export function animalsHaveMuscles() {        return haveMuscles;    }}namespace Animal {    export function doAnimalsHaveMuscles() {        return haveMuscles;  // Error, because haveMuscles is not accessible here    }}

因為 haveMuscles並沒有匯出,只有animalsHaveMuscles函式共享了原始未合併的名稱空間可以訪問這個變數。doAnimalsHaveMuscles函式雖是合併名稱空間的一部分,但是訪問不了未匯出的成員。

名稱空間與類和函式和列舉型別合併

名稱空間可以與其它型別的宣告進行合併。只要名稱空間的定義符合將要合併型別的定義。合併結果包含兩者的宣告型別。TypeScript使用這個功能去實現一些JavaScript裡的設計模式。

合併名稱空間和類

這讓我們可以表示內部類。

class Album {    label: Album.AlbumLabel;}namespace Album {    export class AlbumLabel { }}

合併規則與上面合併名稱空間小節裡講的規則一致,我們必須匯出AlbumLabel類,好讓合併的類能訪問。合併結果是一個類並帶有一個內部類。你也可以使用名稱空間為類增加一些靜態屬性。

除了內部類的模式,你在JavaScript裡,建立一個函式稍後擴充套件它增加一些屬性也是很常見的。TypeScript使用宣告合併來達到這個目的並保證型別安全。

function buildLabel(name: string): string {    return buildLabel.prefix + name + buildLabel.suffix;}namespace buildLabel {    export let suffix = "";    export let prefix = "Hello, ";}console.log(buildLabel("Sam Smith"));

相似的,名稱空間可以用來擴充套件列舉型:

enum Color {    red = 1,    green = 2,    blue = 4}namespace Color {    export function mixColor(colorName: string) {        if (colorName == "yellow") {            return Color.red + Color.green;        }        else if (colorName == "white") {            return Color.red + Color.green + Color.blue;        }        else if (colorName == "magenta") {            return Color.red + Color.blue;        }        else if (colorName == "cyan") {            return Color.green + Color.blue;        }    }}

非法的合併

TypeScript並非允許所有的合併。目前,類不能與其它類或變數合併。想要了解如何模仿類的合併,請參考TypeScript的混入。

模組擴充套件

雖然JavaScript不支援合併,但你可以為匯入的物件打補丁以更新它們。讓我們考察一下這個玩具性的示例:

// observable.jsexport class Observable<T> {    // ... implementation left as an exercise for the reader ...}// map.jsimport { Observable } from "./observable";Observable.prototype.map = function (f) {    // ... another exercise for the reader}

它也可以很好地工作在TypeScript中, 但編譯器對 Observable.prototype.map一無所知。你可以使用擴充套件模組來將它告訴編譯器:

// observable.ts stays the same// map.tsimport { Observable } from "./observable";declare module "./observable" {    interface Observable {        map(f: (x: T) => U): Observable<U>;    }}Observable.prototype.map = function (f) {    // ... another exercise for the reader}// consumer.tsimport { Observable } from "./observable";import "./map";let o: Observable<number>;o.map(x => x.toFixed());

模組名的解析和用import/export解析模組識別符號的方式是一致的。更多資訊請參考Modules。當這些宣告在擴充套件中合併時,就好像在原始位置被聲明瞭一樣。但是,你不能在擴充套件中宣告新的頂級宣告-僅可以擴充套件模組中已經存在的宣告。

全域性擴充套件

你也以在模組內部新增宣告到全域性作用域中。

// observable.tsexport class Observable<T> {    // ... still no implementation ...}declare global {    interface Array<T> {        toObservable(): Observable;    }}Array.prototype.toObservable = function () {    // ...}

全域性擴充套件與模組擴充套件的行為和限制是相同的。

d5292d10430e87959fb438d2c79ee735.png

我們在虛擬的空間與你相遇,期待可以碰撞出不一樣的火花

844a94e5b4a87236c7ee3eb20f0d35c6.png 6c6a6bbefe9f3f2bcf64ffe165790f68.png公眾號ID:前端大聯盟掃碼關注最新動態