命名空间
概述
命名空间(Namespace)是 TypeScript 提供的一种组织代码的方式,用于将相关的代码组织在一起,避免全局作用域污染。虽然现代 TypeScript 项目更推荐使用 ES 模块(import/export),但命名空间仍然有其应用场景,特别是在声明文件(.d.ts)和某些特定场景中。
命名空间通过 namespace 关键字定义,可以包含变量、函数、类、接口等,并通过点号(.)访问命名空间内的成员。
基本语法
定义命名空间
使用 namespace 关键字定义命名空间:
namespace MyNamespace {
export const version = '1.0.0';
export function greet(name: string): string {
return `Hello, ${name}!`;
}
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
}访问命名空间成员
使用点号(.)访问命名空间内的成员:
// 访问命名空间内的成员
console.log(MyNamespace.version); // "1.0.0"
const message = MyNamespace.greet('TypeScript'); // "Hello, TypeScript!"
const calc = new MyNamespace.Calculator();
const result = calc.add(5, 3); // 8注意
只有使用 export 关键字导出的成员才能从命名空间外部访问。未导出的成员只能在命名空间内部使用。
命名空间的作用域
内部和外部访问
命名空间内的成员默认是私有的,只有使用 export 导出的成员才能被外部访问:
namespace Utils {
// 私有变量,外部无法访问
const privateValue = 42;
// 导出变量,外部可以访问
export const publicValue = 100;
// 私有函数
function privateHelper(): void {
console.log('Private helper');
}
// 导出函数
export function publicHelper(): void {
console.log('Public helper');
privateHelper(); // 可以在内部调用私有函数
}
}
// 外部访问
console.log(Utils.publicValue); // 100
Utils.publicHelper(); // "Public helper"
// ❌ 错误:无法访问私有成员
// console.log(Utils.privateValue); // 编译错误
// Utils.privateHelper(); // 编译错误命名空间嵌套
命名空间可以嵌套,形成层次化的结构:
namespace Company {
export namespace HR {
export function hire(name: string): void {
console.log(`Hiring ${name}`);
}
export namespace Benefits {
export function getHealthPlan(): string {
return 'Premium Plan';
}
}
}
export namespace Finance {
export function calculateSalary(base: number): number {
return base * 1.2;
}
}
}
// 访问嵌套命名空间
Company.HR.hire('John'); // "Hiring John"
console.log(Company.HR.Benefits.getHealthPlan()); // "Premium Plan"
const salary = Company.Finance.calculateSalary(5000); // 6000命名空间合并
TypeScript 支持命名空间合并,可以将多个同名的命名空间声明合并为一个:
// 第一个声明
namespace MyLib {
export function func1(): void {
console.log('Function 1');
}
}
// 第二个声明(会自动合并到第一个)
namespace MyLib {
export function func2(): void {
console.log('Function 2');
}
export const version = '1.0.0';
}
// 两个声明合并后,可以访问所有导出的成员
MyLib.func1(); // "Function 1"
MyLib.func2(); // "Function 2"
console.log(MyLib.version); // "1.0.0"命名空间合并对于扩展第三方库的类型定义非常有用:
// 第三方库的类型定义
namespace ThirdPartyLib {
export interface Config {
apiUrl: string;
}
}
// 扩展第三方库的类型
namespace ThirdPartyLib {
export interface ExtendedConfig extends Config {
timeout: number;
retries: number;
}
}
// 使用扩展后的类型
const config: ThirdPartyLib.ExtendedConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};命名空间与接口合并
命名空间可以与同名的接口合并,这对于为接口添加静态成员非常有用:
// 定义接口
interface User {
name: string;
age: number;
}
// 为接口添加静态成员
namespace User {
export function create(name: string, age: number): User {
return { name, age };
}
export function validate(user: User): boolean {
return user.name.length > 0 && user.age > 0;
}
}
// 使用接口和命名空间
const user: User = User.create('John', 30);
const isValid = User.validate(user); // true命名空间与模块
命名空间 vs ES 模块
虽然命名空间和 ES 模块都可以组织代码,但它们有不同的使用场景:
命名空间:
- 主要用于组织代码,避免全局污染
- 适合在单个文件中组织相关代码
- 常用于声明文件(
.d.ts)
ES 模块:
- 现代 TypeScript 项目的推荐方式
- 支持更好的 tree-shaking 和代码分割
- 更适合大型项目
// 使用命名空间(传统方式)
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
}
// 使用 ES 模块(推荐方式)
export function add(a: number, b: number): number {
return a + b;
}在模块中使用命名空间
可以在模块文件中使用命名空间,但需要注意作用域:
// math.ts
export namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
}
// 也可以导出命名空间
export namespace StringUtils {
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}// app.ts - 导入命名空间
import { MathUtils, StringUtils } from './math';
const sum = MathUtils.add(5, 3); // 8
const diff = MathUtils.subtract(10, 4); // 6
const name = StringUtils.capitalize('hello'); // "Hello"提示
虽然可以在模块中使用命名空间,但在现代项目中,更推荐直接导出函数和类,而不是将它们包装在命名空间中。
使用示例
示例 1:创建工具库命名空间
namespace Utils {
export namespace String {
export function capitalize(str: string): string {
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str: string): string {
return str.split('').reverse().join('');
}
}
export namespace Number {
export function format(num: number, decimals: number = 2): string {
return num.toFixed(decimals);
}
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
}
export namespace Date {
export function format(date: Date, format: string = 'YYYY-MM-DD'): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return format.replace('YYYY', String(year))
.replace('MM', month)
.replace('DD', day);
}
}
}
// 使用工具库
const name = Utils.String.capitalize('typescript'); // "TypeScript"
const reversed = Utils.String.reverse('hello'); // "olleh"
const formatted = Utils.Number.format(3.14159, 2); // "3.14"
const clamped = Utils.Number.clamp(150, 0, 100); // 100
const dateStr = Utils.Date.format(new Date()); // "2026-01-XX"示例 2:在声明文件中使用命名空间
命名空间在声明文件(.d.ts)中非常有用,特别是为第三方 JavaScript 库添加类型定义:
// jquery.d.ts
declare namespace jQuery {
interface AjaxSettings {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: any;
success?: (data: any) => void;
error?: (error: any) => void;
}
function ajax(settings: AjaxSettings): void;
namespace fn {
function extend(obj: any): void;
}
}
// 使用
jQuery.ajax({
url: '/api/users',
method: 'GET',
success: (data) => {
console.log(data);
}
});
jQuery.fn.extend({});示例 3:命名空间与类的合并
命名空间可以与类合并,为类添加静态成员:
class User {
constructor(public name: string, public age: number) {}
greet(): string {
return `Hello, I'm ${this.name}`;
}
}
// 为类添加静态方法和属性
namespace User {
export function create(name: string, age: number): User {
return new User(name, age);
}
export function validate(user: User): boolean {
return user.name.length > 0 && user.age >= 0;
}
export const MIN_AGE = 0;
export const MAX_AGE = 150;
}
// 使用实例方法和静态方法
const user = User.create('Alice', 25); // 使用静态方法创建
console.log(user.greet()); // "Hello, I'm Alice"
const isValid = User.validate(user); // true
console.log(User.MAX_AGE); // 150示例 4:命名空间与枚举合并
命名空间可以与枚举合并,为枚举添加方法和属性:
enum Color {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
// 为枚举添加工具方法
namespace Color {
export function getHex(color: Color): string {
const hexMap: Record<Color, string> = {
[Color.Red]: '#FF0000',
[Color.Green]: '#00FF00',
[Color.Blue]: '#0000FF'
};
return hexMap[color];
}
export function isValid(color: string): color is Color {
return Object.values(Color).includes(color as Color);
}
}
// 使用枚举值和命名空间方法
const color = Color.Red;
const hex = Color.getHex(color); // "#FF0000"
const isValid = Color.isValid('red'); // true
const isInvalid = Color.isValid('yellow'); // false命名空间别名
可以使用 import 关键字为命名空间创建别名,简化长命名空间的访问:
namespace VeryLongNamespaceName {
export namespace NestedNamespace {
export function doSomething(): void {
console.log('Doing something');
}
}
}
// 创建别名
import Alias = VeryLongNamespaceName.NestedNamespace;
// 使用别名
Alias.doSomething(); // "Doing something"注意事项
注意
现代项目推荐使用 ES 模块:虽然命名空间仍然可用,但在现代 TypeScript 项目中,更推荐使用 ES 模块(
import/export)来组织代码。命名空间不会创建作用域:命名空间不会创建新的作用域,所有命名空间内的代码都在全局作用域中(除非在模块文件中)。
命名空间合并的限制:命名空间合并时,不能有冲突的导出成员。如果两个命名空间声明中有同名的导出成员,它们的类型必须兼容。
性能考虑:命名空间在编译后会生成更多的代码,而 ES 模块支持更好的 tree-shaking,可以减小打包体积。
模块解析:命名空间的解析方式与模块不同,需要注意
tsconfig.json中的相关配置。
最佳实践
优先使用 ES 模块:在新项目中,优先使用 ES 模块而不是命名空间。
声明文件中使用命名空间:在声明文件(
.d.ts)中,命名空间仍然很有用,特别是为第三方 JavaScript 库添加类型定义。避免深层嵌套:虽然命名空间支持嵌套,但应避免过深的嵌套结构,保持代码清晰。
使用命名空间合并扩展类型:利用命名空间合并特性,可以优雅地扩展第三方库的类型定义。
为长命名空间创建别名:使用
import关键字为长命名空间创建别名,提高代码可读性。
命名空间 vs 模块总结
| 特性 | 命名空间 | ES 模块 |
|---|---|---|
| 作用域 | 全局作用域 | 模块作用域 |
| 代码组织 | 单文件内组织 | 跨文件组织 |
| Tree-shaking | 不支持 | 支持 |
| 推荐使用场景 | 声明文件、小型工具库 | 现代项目、大型应用 |
| 导入方式 | 直接访问 | import/export |
| 编译输出 | 更多代码 | 优化后的代码 |
相关链接
- 模块系统概述 - 了解模块系统的基本概念
- 导入和导出 - 学习 ES 模块的导入和导出
- 模块解析策略 - 深入了解模块解析机制
- 声明合并 - 了解声明合并的详细规则
- TypeScript 官方文档 - 命名空间