TypeScript聲明合并的多種使用方式
聲明是編程語(yǔ)言中的基礎(chǔ)結(jié)構(gòu),它描述了一個(gè)標(biāo)識(shí)符所表示的含義。在TypeScript語(yǔ)言中,一個(gè)標(biāo)識(shí)符總共可以有以下三種含義:
- 表示一個(gè)值。
- 表示一個(gè)類型。
- 表示一個(gè)命名空間。
下例中,我們分別定義了值、類型和命名空間。其中,常量zero屬于值,接口Point屬于類型,而命名空間Utils則屬于命名空間。示例如下:
const zero = 0;
interface Point {
x: number;
y: number;
}
namespace Utils {}
對(duì)于同一個(gè)標(biāo)識(shí)符而言,它可以同時(shí)具有上述多種含義。例如,有一個(gè)標(biāo)識(shí)符A,它可以同時(shí)表示一個(gè)值、一種類型和一個(gè)命名空間。示例如下:
const A = 0;
interface A {}
namespace A {}
在同一聲明空間內(nèi)使用的標(biāo)識(shí)符必須唯一。TypeScript語(yǔ)言中的大部分語(yǔ)法結(jié)構(gòu)都能夠創(chuàng)建出新的聲明空間,例如函數(shù)聲明和類聲明都能夠創(chuàng)建出一個(gè)新的聲明空間。最典型的聲明空間是全局聲明空間和模塊聲明空間。當(dāng)編譯器發(fā)現(xiàn)同一聲明空間內(nèi)存在同名的聲明時(shí),會(huì)嘗試將所有同名的聲明合并為一個(gè)聲明,即聲明合并;若發(fā)現(xiàn)無(wú)法進(jìn)行聲明合并,則會(huì)產(chǎn)生編譯錯(cuò)誤。聲明合并是TypeScript語(yǔ)言特有的行為。在進(jìn)行聲明合并時(shí),編譯器會(huì)按照標(biāo)識(shí)符的含義進(jìn)行分組合并,即值和值合并、類型和類型合并以及命名空間和命名空間合并。但是并非所有同名的聲明都允許進(jìn)行聲明合并,例如,常量聲明a和函數(shù)聲明a之間不會(huì)進(jìn)行聲明合并。
接下來(lái),將具體介紹TypeScript語(yǔ)言中的聲明合并,包括接口聲明、枚舉聲明、類聲明、命名空間聲明、擴(kuò)充模塊聲明、擴(kuò)充全局聲明。
1、接口聲明合并
接口聲明為標(biāo)識(shí)符定義了類型含義。在同一聲明空間內(nèi)聲明的多個(gè)同名接口會(huì)合并成一個(gè)接口聲明。下例中,存在兩個(gè)A接口聲明,它們屬于同一聲明空間。編譯器會(huì)將兩個(gè)A接口聲明中的類型成員合并到一起,合并后的接口A等同于接口MergedA。示例如下:
interface A {
a: string;
}
interface A {
b: number;
}
interface MergedA {
a: string;
b: number;
}
若待合并的接口中存在同名的屬性簽名類型成員,那么這些同名的屬性簽名必須是相同的類型,否則會(huì)因?yàn)楹喜_突而產(chǎn)生編譯錯(cuò)誤。例如,在下例的兩個(gè)接口A中,屬性成員a的類型分別為number類型和string類型,在合并接口時(shí)會(huì)發(fā)生沖突。示例如下:
interface A {
a: number;
}
interface A {
a: string; // 編譯錯(cuò)誤
}
若待合并的接口中存在同名的方法簽名類型成員,那么同名的方法簽名類型成員會(huì)被視為函數(shù)重載,并且靠后定義的方法簽名具有更高的優(yōu)先級(jí)。例如,下例的兩個(gè)A接口聲明中都定義了方法簽名類型成員f。在合并后的接口A中,方法簽名f具有兩個(gè)重載簽名并且后聲明的f方法(第6行)具有更高的優(yōu)先級(jí)。示例如下:
interface A {
f(x: any): void;
}
interface A {
f(x: string): boolean;
}
interface MergedA {
f(x: string): boolean;
f(x: any): void;
}
合并重載簽名的基本原則是后聲明的重載簽名具有更高優(yōu)先級(jí)。但也存在一個(gè)例外,若重載簽名的參數(shù)類型中包含字面量類型,則該重載簽名具有更高的優(yōu)先級(jí)。例如,下例中盡管第2行的方法簽名f是先聲明的,但在接口合并后仍具有更高的優(yōu)先級(jí),因?yàn)樗暮瘮?shù)簽名中帶有字面量類型。示例如下:
interface A {
f(x: 'foo'): boolean;
}
interface A {
f(x: any): void;
}
interface MergedA {
f(x: 'foo'): boolean;
f(x: any): void;
}
若待合并的接口中存在多個(gè)調(diào)用簽名類型成員或構(gòu)造簽名類型成員,那么它們將被視作函數(shù)重載和構(gòu)造函數(shù)重載。與合并方法簽名類型成員相同,后聲明的調(diào)用簽名類型成員和構(gòu)造簽名類型成員具有更高的優(yōu)先級(jí),同時(shí)也會(huì)參考參數(shù)類型中是否包含字面量類型。示例如下:
interface A {
new (x: any): object;
(x: any): any;
}
interface A {
new (x: string): Date;
(x: string): string;
}
interface MergedA {
new (x: string): Date;
new (x: any): object;
(x: string): string;
(x: any): any;
}
當(dāng)涉及重載時(shí),接口合并的順序變得尤為重要,因?yàn)樗鼘⒂绊懼剌d的解析順序。
若待合并的接口中存在多個(gè)字符串索引簽名或數(shù)值索引簽名,則將產(chǎn)生編譯錯(cuò)誤。在合并接口時(shí),所有的接口中只允許存在一個(gè)字符串索引簽名和一個(gè)數(shù)值索引簽名。示例如下:
interface A {
[prop: string]: string;
}
interface A {
[prop: number]: string;
}
interface MergedA {
[prop: string]: string;
[prop: number]: string;
}
若待合并的接口是泛型接口,那么所有同名接口必須有完全相同的類型參數(shù)列表。若待合并的接口存在繼承的接口,那么所有繼承的接口會(huì)被合并為單一的父接口。在實(shí)際程序中,我們應(yīng)避免復(fù)雜接口的合并行為,因?yàn)檫@會(huì)讓代碼變得難以理解。
2、枚舉聲明合并
多個(gè)同名的枚舉聲明會(huì)合并成一個(gè)枚舉聲明。在合并枚舉聲明時(shí),只允許其中一個(gè)枚舉聲明的首個(gè)枚舉成員省略初始值。示例如下:
enum E {
A,
}
enum E {
B = 1,
}
enum E {
C = 2,
}
let e: E;
e = E.A;
e = E.B;
e = E.C;
此例中,第2行的首個(gè)枚舉成員省略了初始值,因此TypeScript會(huì)自動(dòng)計(jì)算初始值。第6行和第10行,必須為首個(gè)枚舉成員定義初始值,否則將產(chǎn)生編譯錯(cuò)誤,因?yàn)榫幾g器無(wú)法在多個(gè)同名枚舉聲明之間自動(dòng)地計(jì)算枚舉值。
枚舉聲明合并的另外一點(diǎn)限制是,多個(gè)同名的枚舉聲明必須同時(shí)為const枚舉或非const枚舉,不允許混合使用。示例如下:
// 正確
enum E0 {
A,
}
enum E0 {
B = 1,
}
// 正確
const enum E1 {
A,
}
const enum E1 {
B = 1,
}
// 編譯錯(cuò)誤
enum E2 {
A,
}
const enum E2 {
B = 1,
}
3、類聲明合并
TypeScript不支持合并同名的類聲明,但是外部類聲明可以與接口聲明進(jìn)行合并,合并后的類型為類類型。示例如下:
declare class C {
x: string;
}
interface C {
y: number;
}
let c: C = new C();
c.x;
c.y;
4、命名空間聲明合并
命名空間的聲明合并會(huì)稍微復(fù)雜一些,它可以與命名空間、函數(shù)、類和枚舉進(jìn)行合并。
4.1 命名空間與命名空間合并
與合并接口類似,同名的命名空間也會(huì)進(jìn)行合并。例如,編譯器會(huì)將下例中的兩個(gè)Animals命名空間進(jìn)行合并,合并后的命名空間等同于命名空間MergedAnimals。示例如下:
namespace Animals {
export class Bird {}
}
namespace Animals {
export interface CanFly {
canFly: boolean;
}
export class Dog {}
}
namespace MergedAnimals {
export interface CanFly {
canFly: boolean;
}
export class Bird {}
export class Dog {}
}
如果存在嵌套的命名空間,那么在合并外層命名空間時(shí),同名的內(nèi)層命名空間也會(huì)進(jìn)行合并。示例如下:
namespace outer {
export namespace inner {
export var x = 10;
}
}
namespace outer {
export namespace inner {
export var y = 20;
}
}
namespace MergedOuter {
export namespace inner {
export var x = 10;
export var y = 20;
}
}
在合并命名空間聲明時(shí),命名空間中的非導(dǎo)出成員不會(huì)被合并,它們只能在各自的命名空間中使用。示例如下:
namespace NS {
const a = 0;
export function foo() {
a; // 正確
}
}
namespace NS {
export function bar() {
foo(); // 正確
a; // 編譯錯(cuò)誤:找不到 'a'
}
}
4.2 命名空間與函數(shù)合并
同名的命名空間聲明與函數(shù)聲明可以進(jìn)行合并,但是要求函數(shù)聲明必須位于命名空間聲明之前,這樣做能夠確保先創(chuàng)建出一個(gè)函數(shù)對(duì)象。函數(shù)與命名空間合并就相當(dāng)于給函數(shù)對(duì)象添加額外的屬性。示例如下:
function f() {
return f.version;
}
namespace f {
export const version = '1.0';
}
f(); // '1.0'
f.version; // '1.0'
4.3 命名空間與類合并
同名的命名空間聲明與類聲明可以進(jìn)行合并,但是要求類聲明必須位于命名空間聲明之前,這樣做能夠確保先創(chuàng)建出一個(gè)構(gòu)造函數(shù)對(duì)象。命名空間與類的合并提供了一種創(chuàng)建內(nèi)部類的方式。示例如下:
class Outer {
inner: Outer.Inner = new Outer.Inner();
}
namespace Outer {
export class Inner {}
}
我們也可以利用命名空間與類的聲明合并來(lái)為類添加靜態(tài)屬性和方法。示例如下:
class A {
foo: string = A.bar;
}
namespace A {
export let bar = 'A';
export function create() {
return new A();
}
}
const a: A = A.create();
a.foo; // 'A'
A.bar; // 'A'
4.4 命名空間與枚舉合并
同名的命名空間聲明與枚舉聲明可以進(jìn)行合并。這相當(dāng)于將枚舉成員與命名空間的導(dǎo)出成員進(jìn)行合并。示例如下:
enum E {
A,
B,
C,
}
namespace E {
export function foo() {
E.A;
E.B;
E.C;
}
}
E.A;
E.B;
E.C;
E.foo();
需要注意的是,枚舉成員名與命名空間導(dǎo)出成員名不允許出現(xiàn)同名的情況。示例如下:
enum E {
A, // 編譯錯(cuò)誤!重復(fù)的標(biāo)識(shí)符 A
}
namespace E {
export function A() {} // 編譯錯(cuò)誤!重復(fù)的標(biāo)識(shí)符 A
}
5、擴(kuò)充模塊聲明
對(duì)于任意模塊,通過(guò)模塊擴(kuò)充語(yǔ)法能夠?qū)δK內(nèi)的已有聲明進(jìn)行擴(kuò)展。例如,在“a.ts”模塊中定義了一個(gè)接口A,在“b.ts”模塊中可以對(duì)“a.ts”模塊中定義的接口A進(jìn)行擴(kuò)展,為其增加新的屬性。
假設(shè)有如下目錄結(jié)構(gòu)的工程:
C:\app |-- a.ts `-- b.ts
“a.ts”模塊文件的內(nèi)容如下:
export interface A {
x: number;
}
“b.ts”模塊文件的內(nèi)容如下:
import { A } from './a';
declare module './a' {
interface A {
y: number;
}
}
const a: A = { x: 0, y: 0 };
此例中,?“declare module ‘./a’ {}”是模塊擴(kuò)充語(yǔ)法。其中,?“‘./a’”表示要擴(kuò)充的模塊名,它與第一行模塊導(dǎo)入語(yǔ)句中的模塊名一致。
我們使用模塊擴(kuò)充語(yǔ)法對(duì)導(dǎo)入模塊“‘./a’”進(jìn)行了擴(kuò)充。第4行定義的接口A將與“a.ts”模塊中的接口A進(jìn)行聲明合并,合并后的結(jié)果仍為接口A,但是接口A增加了一個(gè)屬性成員y。
在進(jìn)行模塊擴(kuò)充時(shí)有以下兩點(diǎn)需要注意:
- 不能在模塊擴(kuò)充語(yǔ)法中增加新的頂層聲明,只能擴(kuò)充現(xiàn)有的聲明。也就是說(shuō),我們只能對(duì)“‘./a’”模塊中已經(jīng)存在的接口A進(jìn)行擴(kuò)充,而不允許增加新的聲明,例如新定義一個(gè)接口B。
- 無(wú)法使用模塊擴(kuò)充語(yǔ)法對(duì)模塊的默認(rèn)導(dǎo)出進(jìn)行擴(kuò)充,只能對(duì)命名模塊導(dǎo)出進(jìn)行擴(kuò)充,因?yàn)樵谶M(jìn)行模塊擴(kuò)充時(shí)需要依賴于導(dǎo)出的名字。
6、擴(kuò)充全局聲明
與模塊擴(kuò)充類似,TypeScript還提供了全局對(duì)象擴(kuò)充語(yǔ)法“declare global {}”?。示例如下:
export {};
declare global {
interface Window {
myAppConfig: object;
}
}
const config: object = window.myAppConfig;
此例中,?“declare global {}”是全局對(duì)象擴(kuò)充語(yǔ)法,它擴(kuò)展了全局的Window對(duì)象,增加了一個(gè)myAppConfig屬性。第1行,我們使用了“export {}”空導(dǎo)出語(yǔ)句,這是因?yàn)槿謱?duì)象擴(kuò)充語(yǔ)句必須在模塊或外部模塊聲明中使用,當(dāng)我們添加了空導(dǎo)出語(yǔ)句后,該文件就成了一個(gè)模塊。
全局對(duì)象擴(kuò)充也具有和模塊擴(kuò)充相同的限制,不能在全局對(duì)象擴(kuò)充語(yǔ)法中增加新的頂層聲明,只能擴(kuò)充現(xiàn)有的聲明。
到此這篇關(guān)于TypeScript聲明合并的多種使用方式的文章就介紹到這了,更多相關(guān)TypeScript聲明合并內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用JS實(shí)現(xiàn)飛機(jī)大戰(zhàn)小游戲
這篇文章主要為大家詳細(xì)介紹了用JS實(shí)現(xiàn)飛機(jī)大戰(zhàn)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
分離式j(luò)avascript取當(dāng)前element值的代碼
比較不錯(cuò)的分離式j(luò)s代碼,獲取element的值,大家注意下,運(yùn)行后的效果是32之類的值,其實(shí)主要是沒(méi)有強(qiáng)制轉(zhuǎn)換成數(shù)字,所以大家可以加上2008-05-05
HTML+JavaScript實(shí)現(xiàn)筋斗云導(dǎo)航欄效果
這篇文章主要為大家介紹了如何利用HTML+JavaScript+CSS實(shí)現(xiàn)筋斗云導(dǎo)航欄效果,當(dāng)鼠標(biāo)經(jīng)過(guò)某個(gè)li,筋斗云跟著到當(dāng)前的位置,感興趣的小伙伴可以試一試2022-03-03
JavaScript實(shí)現(xiàn)動(dòng)態(tài)生成表格
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)動(dòng)態(tài)生成表格,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
JavaScript DOM節(jié)點(diǎn)操作方法總結(jié)
下面小編就為大家?guī)?lái)一篇JavaScript DOM節(jié)點(diǎn)操作方法總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08

