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

