当前位置: 首页 > news >正文

TypeScript——声明合并

声明合并

    • 1、接口声明合并
    • 2、枚举声明合并
    • 3、类声明合并
    • 4、命名空间声明合并
      • 4.1、命名空间与命名空间合并
      • 4.2、 命名空间与函数合并
      • 4.3、 命名空间与类合并
      • 4.4、 命名空间与枚举合并
    • 5、扩充模块声明
    • 6、扩充全局声明

声明是编程语言中的基础结构,它描述了一个标识符所表示的含义。在TypeScript语言中,一个标识符总共可以有以下三种含义:

  • 表示一个值。
  • 表示一个类型。
  • 表示一个命名空间。

下例中,我们分别定义了值、类型和命名空间。其中,常量zero属于值,接口Point属于类型,而命名空间Utils则属于命名空间。示例如下:

constzero=0;interfacePoint{x:number;y:number;}namespaceUtils{}

对于同一个标识符而言,它可以同时具有上述多种含义。例如,有一个标识符A,它可以同时表示一个值、一种类型和一个命名空间。示例如下:

constA=0;interfaceA{}namespaceA{}

在同一声明空间内使用的标识符必须唯一。TypeScript语言中的大部分语法结构都能够创建出新的声明空间,例如函数声明和类声明都能够创建出一个新的声明空间。最典型的声明空间是全局声明空间和模块声明空间。当编译器发现同一声明空间内存在同名的声明时,会尝试将所有同名的声明合并为一个声明,即声明合并;若发现无法进行声明合并,则会产生编译错误。声明合并是TypeScript语言特有的行为。在进行声明合并时,编译器会按照标识符的含义进行分组合并,即值和值合并、类型和类型合并以及命名空间和命名空间合并。但是并非所有同名的声明都允许进行声明合并,例如,常量声明a和函数声明a之间不会进行声明合并。

接下来,将具体介绍TypeScript语言中的声明合并,包括接口声明、枚举声明、类声明、命名空间声明、扩充模块声明、扩充全局声明。

1、接口声明合并

接口声明为标识符定义了类型含义。在同一声明空间内声明的多个同名接口会合并成一个接口声明。下例中,存在两个A接口声明,它们属于同一声明空间。编译器会将两个A接口声明中的类型成员合并到一起,合并后的接口A等同于接口MergedA。示例如下:

interfaceA{a:string;}interfaceA{b:number;}interfaceMergedA{a:string;b:number;}

若待合并的接口中存在同名的属性签名类型成员,那么这些同名的属性签名必须是相同的类型,否则会因为合并冲突而产生编译错误。例如,在下例的两个接口A中,属性成员a的类型分别为number类型和string类型,在合并接口时会发生冲突。示例如下:

interfaceA{a:number;}interfaceA{a:string;// 编译错误}

若待合并的接口中存在同名的方法签名类型成员,那么同名的方法签名类型成员会被视为函数重载,并且靠后定义的方法签名具有更高的优先级。例如,下例的两个A接口声明中都定义了方法签名类型成员f。在合并后的接口A中,方法签名f具有两个重载签名并且后声明的f方法(第6行)具有更高的优先级。示例如下:

interfaceA{f(x:any):void;}interfaceA{f(x:string):boolean;}interfaceMergedA{f(x:string):boolean;f(x:any):void;}

合并重载签名的基本原则是后声明的重载签名具有更高优先级。但也存在一个例外,若重载签名的参数类型中包含字面量类型,则该重载签名具有更高的优先级。例如,下例中尽管第2行的方法签名f是先声明的,但在接口合并后仍具有更高的优先级,因为它的函数签名中带有字面量类型。示例如下:

interfaceA{f(x:'foo'):boolean;}interfaceA{f(x:any):void;}interfaceMergedA{f(x:'foo'):boolean;f(x:any):void;}

若待合并的接口中存在多个调用签名类型成员或构造签名类型成员,那么它们将被视作函数重载和构造函数重载。与合并方法签名类型成员相同,后声明的调用签名类型成员和构造签名类型成员具有更高的优先级,同时也会参考参数类型中是否包含字面量类型。示例如下:

interfaceA{new(x:any):object;(x:any):any;}interfaceA{new(x:string):Date;(x:string):string;}interfaceMergedA{new(x:string):Date;new(x:any):object;(x:string):string;(x:any):any;}

当涉及重载时,接口合并的顺序变得尤为重要,因为它将影响重载的解析顺序。

若待合并的接口中存在多个字符串索引签名或数值索引签名,则将产生编译错误。在合并接口时,所有的接口中只允许存在一个字符串索引签名和一个数值索引签名。示例如下:

interfaceA{[prop:string]:string;}interfaceA{[prop:number]:string;}interfaceMergedA{[prop:string]:string;[prop:number]:string;}

若待合并的接口是泛型接口,那么所有同名接口必须有完全相同的类型参数列表。若待合并的接口存在继承的接口,那么所有继承的接口会被合并为单一的父接口。在实际程序中,我们应避免复杂接口的合并行为,因为这会让代码变得难以理解。

2、枚举声明合并

多个同名的枚举声明会合并成一个枚举声明。在合并枚举声明时,只允许其中一个枚举声明的首个枚举成员省略初始值。示例如下:

enumE{A,}enumE{B=1,}enumE{C=2,}lete:E;e=E.A;e=E.B;e=E.C;

此例中,第2行的首个枚举成员省略了初始值,因此TypeScript会自动计算初始值。第6行和第10行,必须为首个枚举成员定义初始值,否则将产生编译错误,因为编译器无法在多个同名枚举声明之间自动地计算枚举值。

枚举声明合并的另外一点限制是,多个同名的枚举声明必须同时为const枚举或非const枚举,不允许混合使用。示例如下:

// 正确enumE0{A,}enumE0{B=1,}// 正确constenumE1{A,}constenumE1{B=1,}// 编译错误enumE2{A,}constenumE2{B=1,}

3、类声明合并

TypeScript不支持合并同名的类声明,但是外部类声明可以与接口声明进行合并,合并后的类型为类类型。示例如下:

declareclassC{x:string;}interfaceC{y:number;}letc:C=newC();c.x;c.y;

4、命名空间声明合并

命名空间的声明合并会稍微复杂一些,它可以与命名空间、函数、类和枚举进行合并。

4.1、命名空间与命名空间合并

与合并接口类似,同名的命名空间也会进行合并。例如,编译器会将下例中的两个Animals命名空间进行合并,合并后的命名空间等同于命名空间MergedAnimals。示例如下:

namespaceAnimals{exportclassBird{}}namespaceAnimals{exportinterfaceCanFly{canFly:boolean;}exportclassDog{}}namespaceMergedAnimals{exportinterfaceCanFly{canFly:boolean;}exportclassBird{}exportclassDog{}}

如果存在嵌套的命名空间,那么在合并外层命名空间时,同名的内层命名空间也会进行合并。示例如下:

namespaceouter{exportnamespaceinner{exportvarx=10;}}namespaceouter{exportnamespaceinner{exportvary=20;}}namespaceMergedOuter{exportnamespaceinner{exportvarx=10;exportvary=20;}}

在合并命名空间声明时,命名空间中的非导出成员不会被合并,它们只能在各自的命名空间中使用。示例如下:

namespaceNS{consta=0;exportfunctionfoo(){a;// 正确}}namespaceNS{exportfunctionbar(){foo();// 正确a;// 编译错误:找不到 'a'}}

4.2、 命名空间与函数合并

同名的命名空间声明与函数声明可以进行合并,但是要求函数声明必须位于命名空间声明之前,这样做能够确保先创建出一个函数对象。函数与命名空间合并就相当于给函数对象添加额外的属性。示例如下:

functionf(){returnf.version;}namespacef{exportconstversion='1.0';}f();// '1.0'f.version;// '1.0'

4.3、 命名空间与类合并

同名的命名空间声明与类声明可以进行合并,但是要求类声明必须位于命名空间声明之前,这样做能够确保先创建出一个构造函数对象。命名空间与类的合并提供了一种创建内部类的方式。示例如下:

classOuter{inner:Outer.Inner=newOuter.Inner();}namespaceOuter{exportclassInner{}}

我们也可以利用命名空间与类的声明合并来为类添加静态属性和方法。示例如下:

classA{foo:string=A.bar;}namespaceA{exportletbar='A';exportfunctioncreate(){returnnewA();}}consta:A=A.create();a.foo;// 'A'A.bar;// 'A'

4.4、 命名空间与枚举合并

同名的命名空间声明与枚举声明可以进行合并。这相当于将枚举成员与命名空间的导出成员进行合并。示例如下:

enumE{A,B,C,}namespaceE{exportfunctionfoo(){E.A;E.B;E.C;}}E.A;E.B;E.C;E.foo();

需要注意的是,枚举成员名与命名空间导出成员名不允许出现同名的情况。示例如下:

enumE{A,// 编译错误!重复的标识符 A}namespaceE{exportfunctionA(){}// 编译错误!重复的标识符 A}

5、扩充模块声明

对于任意模块,通过模块扩充语法能够对模块内的已有声明进行扩展。例如,在“a.ts”模块中定义了一个接口A,在“b.ts”模块中可以对“a.ts”模块中定义的接口A进行扩展,为其增加新的属性。

假设有如下目录结构的工程:

C:\app |-- a.ts `-- b.ts

“a.ts”模块文件的内容如下:

exportinterfaceA{x:number;}

“b.ts”模块文件的内容如下:

import{A}from'./a';declaremodule'./a'{interfaceA{y:number;}}consta:A={x:0,y:0};

此例中,​“declare module ‘./a’ {}”是模块扩充语法。其中,​“‘./a’”表示要扩充的模块名,它与第一行模块导入语句中的模块名一致。

我们使用模块扩充语法对导入模块“‘./a’”进行了扩充。第4行定义的接口A将与“a.ts”模块中的接口A进行声明合并,合并后的结果仍为接口A,但是接口A增加了一个属性成员y。

在进行模块扩充时有以下两点需要注意:

  • 不能在模块扩充语法中增加新的顶层声明,只能扩充现有的声明。也就是说,我们只能对“‘./a’”模块中已经存在的接口A进行扩充,而不允许增加新的声明,例如新定义一个接口B。
  • 无法使用模块扩充语法对模块的默认导出进行扩充,只能对命名模块导出进行扩充,因为在进行模块扩充时需要依赖于导出的名字。

6、扩充全局声明

与模块扩充类似,TypeScript还提供了全局对象扩充语法“declare global {}”​。示例如下:

export{};declareglobal{interfaceWindow{myAppConfig:object;}}constconfig:object=window.myAppConfig;

此例中,​“declare global {}”是全局对象扩充语法,它扩展了全局的Window对象,增加了一个myAppConfig属性。第1行,我们使用了“export {}”空导出语句,这是因为全局对象扩充语句必须在模块或外部模块声明中使用,当我们添加了空导出语句后,该文件就成了一个模块。

全局对象扩充也具有和模块扩充相同的限制,不能在全局对象扩充语法中增加新的顶层声明,只能扩充现有的声明。

http://www.jsqmd.com/news/557466/

相关文章:

  • 学术圈大地震!CCF号召抵制NeurIPS,国产AI如何重构科研话语权?
  • HT1621B驱动LCD屏实战:从硬件连接到代码调试全流程(附常见问题排查)
  • HTML---基本标签2
  • 泛型的难点解释
  • 2026智慧综合能源方案优质品牌推荐指南:能耗计量电表/远程抄表电表/远程电力抄表/逆流监测电表/零碳园区能源方案/选择指南 - 优质品牌商家
  • 使用GeoTools把Geojson转换成Shp文件
  • 新手必看!华为云Nginx服务搭建从入门到放弃的5个关键步骤
  • 面向对象的I²C驱动封装设计与实现
  • TypeScript——编译器和编译选项
  • 降AI率工具语义重构技术解读:为何能有效降论文AIGC率
  • 从Corner到Scenario:一次讲透MCMM中工艺角(ss/tt/ff)与场景绑定的实战配置
  • 从零开始搭建苍穹外卖项目:手把手教你配置前后端开发环境(含Nginx避坑指南)
  • TypeScript——tsconfig.json
  • 电子课本智能解析:教育工作者的高效资源获取解决方案
  • Simulink子系统组件切换实战:从Demo到自定义模型的完整指南
  • 中国全国土壤有机碳密度数据集(2010-2024年)
  • Carla自动驾驶模拟器快捷键大全:从手动控制到天气切换
  • 2026高校AIGC政策全面收紧,毕业生如何高效降论文ai率应对?
  • 别再死记硬背了!图解‘快慢指针’和‘对撞指针’,5分钟理解两种核心思想
  • 成都单元门优质品牌推荐:防火窗、防爆门、防盗门、隔音门、不锈钢门、保温门、别墅大门、医院门、实木门、室内套装木门选择指南 - 优质品牌商家
  • ubuntu安装openclaw接入智谱大模型和微信QQ通道配置
  • TypeScript——工程引用
  • OpenClaw调试技巧:百川2-13B任务失败时的日志分析与问题定位
  • Seelen-UI桌面定制引擎:3步打造专属Windows工作空间
  • 告别误报!用FR2V H00磁通门传感器搞定充电桩直流漏电检测(附IEC 62955标准解读)
  • 每日漫图 v2.8.2-4K超清画质+大量精品画作,换壁纸就来这里
  • 5个核心功能实现全球多语言语音降噪:基于深度滤波的开源解决方案
  • 如何高效管理DLSS版本:提升游戏性能的实用指南
  • TypeScript——JavaScript类型检查
  • 如何快速优化AMD系统:5个实用技巧让Ryzen性能更稳定