Skip to content

命名空间

概述

命名空间(Namespace)是 TypeScript 提供的一种组织代码的方式,用于将相关的代码组织在一起,避免全局作用域污染。虽然现代 TypeScript 项目更推荐使用 ES 模块(import/export),但命名空间仍然有其应用场景,特别是在声明文件(.d.ts)和某些特定场景中。

命名空间通过 namespace 关键字定义,可以包含变量、函数、类、接口等,并通过点号(.)访问命名空间内的成员。

基本语法

定义命名空间

使用 namespace 关键字定义命名空间:

typescript
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;
    }
  }
}

访问命名空间成员

使用点号(.)访问命名空间内的成员:

typescript
// 访问命名空间内的成员
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 导出的成员才能被外部访问:

typescript
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();            // 编译错误

命名空间嵌套

命名空间可以嵌套,形成层次化的结构:

typescript
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 支持命名空间合并,可以将多个同名的命名空间声明合并为一个:

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"

命名空间合并对于扩展第三方库的类型定义非常有用:

typescript
// 第三方库的类型定义
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
};

命名空间与接口合并

命名空间可以与同名的接口合并,这对于为接口添加静态成员非常有用:

typescript
// 定义接口
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 和代码分割
  • 更适合大型项目
typescript
// 使用命名空间(传统方式)
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;
}

在模块中使用命名空间

可以在模块文件中使用命名空间,但需要注意作用域:

typescript
// 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);
  }
}
typescript
// 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:创建工具库命名空间

typescript
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 库添加类型定义:

typescript
// 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:命名空间与类的合并

命名空间可以与类合并,为类添加静态成员:

typescript
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:命名空间与枚举合并

命名空间可以与枚举合并,为枚举添加方法和属性:

typescript
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 关键字为命名空间创建别名,简化长命名空间的访问:

typescript
namespace VeryLongNamespaceName {
  export namespace NestedNamespace {
    export function doSomething(): void {
      console.log('Doing something');
    }
  }
}

// 创建别名
import Alias = VeryLongNamespaceName.NestedNamespace;

// 使用别名
Alias.doSomething(); // "Doing something"

注意事项

注意

  1. 现代项目推荐使用 ES 模块:虽然命名空间仍然可用,但在现代 TypeScript 项目中,更推荐使用 ES 模块(import/export)来组织代码。

  2. 命名空间不会创建作用域:命名空间不会创建新的作用域,所有命名空间内的代码都在全局作用域中(除非在模块文件中)。

  3. 命名空间合并的限制:命名空间合并时,不能有冲突的导出成员。如果两个命名空间声明中有同名的导出成员,它们的类型必须兼容。

  4. 性能考虑:命名空间在编译后会生成更多的代码,而 ES 模块支持更好的 tree-shaking,可以减小打包体积。

  5. 模块解析:命名空间的解析方式与模块不同,需要注意 tsconfig.json 中的相关配置。

最佳实践

  1. 优先使用 ES 模块:在新项目中,优先使用 ES 模块而不是命名空间。

  2. 声明文件中使用命名空间:在声明文件(.d.ts)中,命名空间仍然很有用,特别是为第三方 JavaScript 库添加类型定义。

  3. 避免深层嵌套:虽然命名空间支持嵌套,但应避免过深的嵌套结构,保持代码清晰。

  4. 使用命名空间合并扩展类型:利用命名空间合并特性,可以优雅地扩展第三方库的类型定义。

  5. 为长命名空间创建别名:使用 import 关键字为长命名空间创建别名,提高代码可读性。

命名空间 vs 模块总结

特性命名空间ES 模块
作用域全局作用域模块作用域
代码组织单文件内组织跨文件组织
Tree-shaking不支持支持
推荐使用场景声明文件、小型工具库现代项目、大型应用
导入方式直接访问import/export
编译输出更多代码优化后的代码

相关链接

基于 VitePress 构建