在TypeScript的编程世界里,命名空间(Namespace)是一种用于组织相关代码的有效方式。它就像是一个容器🧳,把相关的变量、函数、类和接口等代码元素整合在一起,避免命名冲突,提高代码的可维护性和可读性。不过,需要注意的是,随着ES模块的出现,官方已经不推荐使用命名空间了,但了解它仍然有助于我们更好地理解TypeScript的发展历程和代码结构。
命名空间的基本用法📦
命名空间的主要作用是创建一个封闭的作用域,在这个作用域内定义的变量和函数等,默认情况下只能在该命名空间内部使用。我们来看下面这个例子:
namespace Utils {
function isString(value:any) {
return typeof value === 'string';
}
// 正确,在命名空间内部调用
isString('yes');
}
// 报错,在命名空间外部调用
Utils.isString('no');
在上面的代码中,Utils
命名空间里定义了isString
函数,它在Utils
内部可以正常使用,但如果在外部直接调用就会报错。
要是想在命名空间之外使用其内部成员,就得给该成员加上export
前缀,这样它就变成了对外输出的接口。比如:
namespace Utility {
export function log(msg:string) {
console.log(msg);
}
export function error(msg:string) {
console.error(msg);
}
}
Utility.log('Call me');
Utility.error('maybe!');
加上export
前缀后,log
和error
函数就能在Utility
命名空间外部被调用了。编译后的JavaScript代码如下:
var Utility;
(function (Utility) {
function log(msg) {
console.log(msg);
}
Utility.log = log;
function error(msg) {
console.error(msg);
}
Utility.error = error;
})(Utility || (Utility = {}));
从编译后的代码可以看出,命名空间Utility
变成了JavaScript中的一个对象,使用export
的内部成员都成了这个对象的属性。这意味着命名空间不仅仅是类型代码,它的值会保留在编译后的代码中,使用时需要特别注意。
在命名空间内部,还可以使用import
命令引入外部成员,并为其起别名。这在外部成员名称较长时,能有效简化代码。例如:
namespace Utils {
export function isString(value:any) {
return typeof value === 'string';
}
}
namespace App {
import isString = Utils.isString;
isString('yes');
// 等同于
Utils.isString('yes');
}
这里,import
命令在App
命名空间内,为Utils.isString
函数指定了别名isString
,使用起来更加方便。import
命令也能在命名空间外部使用,为命名空间或其成员指定别名:
namespace Shapes {
export namespace Polygons {
export class Triangle {}
export class Square {}
}
}
import polygons = Shapes.Polygons;
// 等同于 new Shapes.Polygons.Square()
let sq = new polygons.Square();
在这个例子中,import
命令在Shapes
命名空间外部,将Shapes.Polygons
的别名指定为polygons
。
命名空间还支持嵌套使用,就像俄罗斯套娃一样🎁。例如:
namespace Utils {
export namespace Messaging {
export function log(msg:string) {
console.log(msg);
}
}
}
Utils.Messaging.log('hello') // "hello"
在这个例子中,Utils
命名空间内部嵌套了Messaging
命名空间。如果要在外部使用Messaging
,必须加上export
命令,而且引用时要从最外层开始,如Utils.Messaging.log()
。
命名空间不仅能包含实际的代码逻辑,还能包含类型代码,比如接口和类:
namespace N {
export interface MyInterface{}
export class MyClass{}
}
这里,N
命名空间对外输出了接口MyInterface
和类MyClass
,它们都可以作为类型使用。
虽然命名空间和模块都能组织相关代码并对外输出接口,但它们还是有区别的。一个文件中只能有一个模块,但可以有多个命名空间。由于模块是JavaScript的标准语法,并且不需要编译转换,所以更推荐使用模块来替代命名空间。
要是命名空间代码写在单独的文件里,引入这个文件时需要使用三斜杠语法:
/// <reference path = "SomeFileName.ts" />
命名空间的输出📤
命名空间本身也可以通过export
命令输出,供其他文件使用。例如,在shapes.ts
文件中:
// shapes.ts
export namespace Shapes {
export class Triangle {
// ...
}
export class Square {
// ...
}
}
在其他脚本文件中,可以使用import
命令加载这个命名空间:
// 写法一
import { Shapes } from './shapes';
let t = new Shapes.Triangle();
// 写法二
import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle();
不过,还是建议优先使用模块的输出和输入方式,这样代码更简洁、规范:
// shapes.ts
export class Triangle {
/* ... */
}
export class Square {
/* ... */
}
// shapeConsumer.ts
import * as shapes from "./shapes";
let t = new shapes.Triangle();
命名空间的合并📏
TypeScript中,多个同名的命名空间会自动合并,这和接口的合并类似。比如:
namespace Animals {
export class Cat {}
}
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Dog {}
}
// 等同于
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Cat {}
export class Dog {}
}
这种合并机制非常实用,如果同名的命名空间分布在不同文件中,TypeScript会将它们合并在一起,方便我们扩展代码。但要注意,合并命名空间时,非export
的成员不会被合并,而且只能在各自的命名空间中使用:
namespace N {
const a = 0;
export function foo() {
console.log(a); // 正确
}
}
namespace N {
export function bar() {
foo(); // 正确
console.log(a); // 报错
}
}
在这个例子中,变量a
是第一个N
命名空间的非对外成员,只能在第一个命名空间内使用。
命名空间还能和同名函数、类、枚举合并,但要求同名函数、类、枚举必须在命名空间之前声明。这是为了先创建函数、类或枚举对象,然后同名命名空间就可以为它们添加额外的属性或方法。
function f() {
return f.version;
}
namespace f {
export const version = '1.0';
}
f() // '1.0'
f.version // '1.0'
这里,函数f
和命名空间f
合并,命名空间为函数对象f
添加了version
属性。
class C {
foo = 1;
}
namespace C {
export const bar = 2;
}
C.bar // 2
在这个例子中,命名空间C
为类C
添加了静态属性bar
。
enum E {
A,
B,
C,
}
namespace E {
export function foo() {
console.log(E.C);
}
}
E.foo() // 2
命名空间E
为枚举E
添加了foo
方法。但要注意,枚举成员和命名空间导出成员不允许同名,否则会报错:
enum E {
A, // 报错
B,
}
namespace E {
export function A() {} // 报错
}
通过以上对TypeScript命名空间的深入了解,我们掌握了它的基本用法、输出方式和合并规则。虽然现在官方更推荐使用模块,但命名空间在一些特定场景或旧代码库中仍然有其价值。希望大家在编程过程中,根据实际情况灵活选择合适的代码组织方式,编写出更优秀的代码💻。