TypeScript——命名空间

张开发
2026/4/23 6:08:57 15 分钟阅读

分享文章

TypeScript——命名空间
命名空间1、概述2、命名空间声明3、导出命名空间内的声明4、别名导入声明5、在多文件中使用命名空间5.1、文件间的依赖5.2、tsconfig.json5.3、三斜线指令1、概述在ECMAScript 2015之前JavaScript语言没有内置的模块支持。在JavaScript程序中通常使用“命名空间”来组织并隔离代码以免产生命名冲突等问题。最为流行的实现命名空间的方法是使用立即执行的函数表达式。这是因为立即执行的函数表达式能够创建出一个新的作用域并且不会对外层作用域产生影响。下例中使用立即执行的函数表达式定义了两个命名空间在这两个命名空间中定义的变量x不会相互冲突。示例如下(function(){constx0;})();(function(){constx{message:hello world};})();TypeScript利用了这个经典的命名空间实现方式并提供了声明命名空间的简便语法。2、命名空间声明命名空间通过namespace关键字来声明它相当于一种语法糖。示例如下namespaceUtils{functionisString(value:any){returntypeofvaluestring;}}此例中我们声明了一个名为Utils的命名空间。这段TypeScript代码在编译后将生成如下JavaScript代码// output.jsuse strict;varUtils;(function(Utils){functionisString(value){returntypeofvaluestring;}})(Utils||(Utils{}));我们能够看到命名空间被转换成了立即执行的函数表达式。在定义命名空间的名字时允许使用以点符号“.”分隔的名字这与其他编程语言中的命名空间声明类似。示例如下namespaceSystem.Utils{functionisString(value:any){returntypeofvaluestring;}}此例中定义的命名空间相当于两个嵌套的命名空间声明它等同于如下的代码namespaceSystem{exportnamespaceUtils{functionisString(value:any){returntypeofvaluestring;}}}在命名空间内部可以使用绝大多数语言功能如变量声明、函数声明、接口声明和命名空间声明等。示例如下namespaceOuter{namespaceInner{consta0;typeNullableTT|undefined|null;interfacePoint{x:number;y:number;}classCat{name:string;}functionf(p:Point){console.log(p.x);}}}3、导出命名空间内的声明默认情况下在命名空间内部的声明只允许在该命名空间内部使用在命名空间之外访问命名空间内部的声明会产生错误。示例如下namespaceUtils{functionisString(value:any){returntypeofvaluestring;}// 正确isString(yes);}Utils.isString(no);// ~~~~~~~~// 编译错误Utils中不存在isString属性如果我们查看由此例中的TypeScript代码生成的JavaScript代码那么就能够明白为什么这段代码会产生错误示例如下// output.jsvarUtils;(function(Utils){functionisString(value){returntypeofvaluestring;}isString(yes);})(Utils||(Utils{}));Utils.isString(no);// 运行错误通过分析生成的JavaScript代码能够发现isString仅存在于立即执行的函数表达式的内部作用域在外部作用域不允许访问内部作用域中的声明。如果想要让命名空间内部的某个声明在命名空间外部也能够使用则需要使用导出声明语句明确地导出该声明。导出命名空间内的声明需要使用export关键字示例如下namespaceUtils{exportfunctionisString(value:any){returntypeofvaluestring;}// 正确isString(yes);}// 正确Utils.isString(yes);此例中我们使用export关键字导出了isString函数声明。因此在Utils外部也可以使用isString函数。此例中的代码生成的JavaScript代码如下所示// output.jsvarUtils;(function(Utils){functionisString(value){returntypeofvaluestring;}Utils.isStringisString;isString(yes);})(Utils||(Utils{}));Utils.isString(yes);在访问导出的命名空间声明时需要使用命名空间名和导出声明名并用点符号连接这类似于对象属性访问的语法。4、别名导入声明我们可以使用import语句为命名空间的导出声明起一个别名。当命名空间名字比较长时使用别名能够有效地简化代码。示例如下namespaceUtils{exportfunctionisString(value:any){returntypeofvaluestring;}}namespaceApp{importisStringUtils.isString;isString(yes);Utils.isString(yes);}此例中在App命名空间中为从Utils命名空间中导出的isString函数声明设置了一个别名isString这样就可以像第10行一样使用isString来引用“Utils.isString”函数而不必像第12行那样写出完整的访问路径。别名导入本质上“相当于”新声明了一个变量并将导出声明赋值给该变量。例如上例中的代码编译后生成的JavaScript代码如下所示use strict;varUtils;(function(Utils){functionisString(value){returntypeofvaluestring;}Utils.isStringisString;})(Utils||(Utils{}));varApp;(function(App){varisStringUtils.isString;// 别名导入声明isString(yes);Utils.isString(yes);})(App||(App{}));需要注意的是别名导入只是相当于新声明了一个变量而已实际上不完全是这样的因为别名导入对类型也有效。示例如下namespaceUtils{exportinterfacePoint{x:number;y:number;}}namespaceApp{importPointUtils.Point;constp:Point{x:0,y:0};}此例中的代码编译后生成的JavaScript代码如下所示// output.jsuse strict;varApp;(function(App){constp{x:0,y:0};})(App||(App{}));5、在多文件中使用命名空间在实际工程中代码不可能都放在同一个文件中一定会拆分到不同的源代码文件。我们也可以将同一个命名空间声明拆分到不同的文件中TypeScript最终会将同名的命名空间声明合并在一起。例如在如下两个文件中声明了同名的命名空间。“a.ts”文件的内容如下namespaceUtils{exportfunctionisString(value:any){returntypeofvaluestring;}exportinterfacePoint{x:number;y:number;}}“b.ts”文件的内容如下namespaceUtils{exportfunctionisNumber(value:any){returntypeofvaluenumber;}}最终合并后的Utils命名空间中存在三个导出声明isString、isNumber和Point。5.1、文件间的依赖当我们将命名空间拆分到不同的文件后需要注意文件的加载顺序因为文件之间可能存在依赖关系。例如有两个拆分后的文件a.ts和b.ts。“a.ts”文件的内容如下namespaceApp{exportfunctionisString(value:any){returntypeofvaluestring;}}“b.ts”文件的内容如下namespaceApp{constaisString(foo);}这两个文件中​“b.ts”依赖于“a.ts”​。因为“b.ts”中调用了“a.ts”中定义的方法。我们需要保证“a.ts”先于“b.ts”被加载否则在执行“b.ts”中的代码时将产生isString未定义的错误。定义文件间的依赖关系有多种方式介绍以下两种使用tsconfig.json文件。使用三斜线指令。5.2、tsconfig.json通过“tsconfig.json”配置文件能够定义文件间的加载顺序。例如通过如下的配置文件能够定义“a.ts”先于“b.ts”被加载这里我们主要配置了outFile和files两个选项。示例如下{compilerOptions:{strict:true,target:ESNext,outFile:main.js},files:[a.ts,b.ts]}首先outFile选项指定了编译后输出的文件名。在指定了该选项后编译后的“a.ts”和“b.ts”文件将被合并成一个“main.js”文件。其次files选项指定了工程中包含的所有源文件。files文件列表是有序列表我们正是通过它来保证“a.ts”先于“b.ts”被加载。最终编译后输出的“main.js”内容如下use strict;// a.tsvarApp;(function(App){functionisString(value){returntypeofvaluestring;}App.isStringisString;})(App||(App{}));// b.tsvarApp;(function(App){constaApp.isString(foo);})(App||(App{}));由该输出文件能够看到“a.ts”位于“b.ts”之前因此不会产生错误。5.3、三斜线指令三斜线指令是TypeScript早期版本中就支持的一个特性我们可以通过它来定义文件间的依赖。三斜线指令的形式如下所示/// reference patha.ts /此例中的三斜线指令声明了对“a.ts”文件的依赖。我们可以在“b.ts”中使用三斜线指令来声明对“a.ts”文件的依赖。“a.ts”文件的内容如下namespaceApp{exportfunctionisString(value:any){returntypeofvaluestring;}}“b.ts”文件的内容如下/// reference patha.ts /namespaceApp{constaisString(foo);}在使用了三斜线指令后编译器能够识别出“b.ts”依赖于“a.ts”​。在编译“b.ts”之前编译器会确保先编译“a.ts”​。就算在tsconfig.json配置文件的files选项中将“b.ts”放在了“a.ts”之前编译器也能够识别出正确的依赖顺序。示例如下{compilerOptions:{strict:true,target:ESNext,outFile:main.js},files:[b.ts,a.ts]}我们甚至都不需要在files选项中包含“a.ts”文件只需要包含“b.ts”即可。因为在编译“b.ts”时编译器将保证依赖的文件会一同被编译。示例如下{compilerOptions:{strict:true,target:ESNext,outFile:main.js},files:[b.ts]}使用以上两个例子中的tsconfig.json配置文件都能够得到正确且相同的输出文件“main.js”​。示例如下use strict;// a.tsvarApp;(function(App){functionisString(value){returntypeofvaluestring;}App.isStringisString;})(App||(App{}));// b.ts/// reference patha.ts /varApp;(function(App){constaApp.isString(foo);})(App||(App{}));

更多文章