typescript:第十二章_命名空间

第十二章:命名空间

命名空间(Namespace)是 TypeScript 早期版本中用于组织代码和组织类型命名冲突的重要特性。虽然在 ES6 模块普及后,模块已成为首选的代码组织方式,但命名空间在某些场景下仍然非常有用,特别是在声明文件和全局扩展中。本章将详细介绍命名空间的概念、用法以及与模块的区别。

命名空间是一种将相关代码组织在一起的方式,它可以包含类、接口、函数、变量等。命名空间通过创建一个新的作用域来避免命名冲突。

// 定义命名空间
namespace MyNamespace {
  export const version = "1.0.0";
 
  export class User {
    constructor(public name: string) {}
  }
 
  export interface Config {
    timeout: number;
  }
 
  export function greet(name: string): string {
    return `Hello, ${name}!`;
  }
}
 
// 使用命名空间
console.log(MyNamespace.version);
const user = new MyNamespace.User("Alice");
console.log(MyNamespace.greet("World"));

在没有模块系统(ES6 Modules)之前,JavaScript 代码通常使用全局变量,这容易导致命名冲突:

// 不使用命名空间 - 全局命名冲突风险
// library-a.ts
function processData(data: any) { /* ... */ }
 
// library-b.ts
function processData(data: any) { /* ... */ }  // 冲突!
 
// 使用命名空间解决冲突
namespace LibraryA {
  export function processData(data: any) { /* ... */ }
}
 
namespace LibraryB {
  export function processData(data: any) { /* ... */ }
}
 
// 使用
LibraryA.processData({});
LibraryB.processData({});
namespace Validation {
  // 内部变量 - 不导出则外部无法访问
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 
  // 导出接口
  export interface StringValidator {
    isValid(s: string): boolean;
  }
 
  // 导出类
  export class EmailValidator implements StringValidator {
    isValid(s: string): boolean {
      return emailRegex.test(s);
    }
  }
 
  export class PhoneValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^\d{11}$/.test(s);
    }
  }
 
  // 导出函数
  export function validateAll(
    validators: StringValidator[],
    value: string
  ): boolean {
    return validators.every(v => v.isValid(value));
  }
}
 
// 使用
const emailValidator = new Validation.EmailValidator();
console.log(emailValidator.isValid("test@example.com"));  // true

命名空间可以嵌套定义,形成层级结构:

namespace App {
  export namespace Utils {
    export function formatDate(date: Date): string {
      return date.toISOString().split('T')[0];
    }
 
    export function generateId(): string {
      return Math.random().toString(36).substr(2, 9);
    }
  }
 
  export namespace Models {
    export interface User {
      id: string;
      name: string;
    }
 
    export class UserRepository {
      private users: User[] = [];
 
      add(user: User): void {
        this.users.push(user);
      }
 
      findById(id: string): User | undefined {
        return this.users.find(u => u.id === id);
      }
    }
  }
}
 
// 使用嵌套命名空间
const formattedDate = App.Utils.formatDate(new Date());
const userRepo = new App.Models.UserRepository();
 
userRepo.add({
  id: App.Utils.generateId(),
  name: "Alice"
});

对于深层嵌套的命名空间,可以使用 import 别名简化访问:

namespace Company {
  export namespace Department {
    export namespace Team {
      export class Member {
        constructor(public name: string) {}
      }
 
      export function createTeam(name: string): Member[] {
        return [];
      }
    }
  }
}
 
// 使用别名
import Team = Company.Department.Team;
 
const member = new Team.Member("Alice");
const newTeam = Team.createTeam("Engineering");
 
// 等价于
const member2 = new Company.Department.Team.Member("Bob");

一个文件可以包含多个命名空间:

// types.ts
namespace Types {
  export interface Point {
    x: number;
    y: number;
  }
 
  export type ID = string | number;
}
 
namespace Helpers {
  export function distance(p1: Types.Point, p2: Types.Point): number {
    const dx = p1.x - p2.x;
    const dy = p1.y - p2.y;
    return Math.sqrt(dx * dx + dy * dy);
  }
}
 
// 使用
const p1: Types.Point = { x: 0, y: 0 };
const p2: Types.Point = { x: 3, y: 4 };
console.log(Helpers.distance(p1, p2));  // 5

命名空间可以分散在多个文件中,通过 “ <reference path=”…“ >” 指令连接:

// validation/StringValidator.ts
namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }
}
// validation/EmailValidator.ts
/// <reference path="StringValidator.ts" />
namespace Validation {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 
  export class EmailValidator implements StringValidator {
    isValid(s: string): boolean {
      return emailRegex.test(s);
    }
  }
}
// validation/ZipCodeValidator.ts
/// <reference path="StringValidator.ts" />
namespace Validation {
  export class ZipCodeValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^\d{6}$/.test(s);
    }
  }
}

编译多文件命名空间需要特殊配置:

{
  "compilerOptions": {
    "outFile": "./dist/bundle.js",  // 将所有文件合并输出
    "module": "AMD",  // 或使用 System
    "declaration": true  // 生成 .d.ts 声明文件
  }
}
 
===== 12.4 命名空间与模块的区别 =====
 
==== 12.4.1 关键区别 ====
 
| 特性 | 命名空间 (Namespace) | 模块 (Module) |
|------|---------------------|---------------|
| 语法 | namespace XXX {} | export / import |
| 编译输出 | 合并为单个文件 (outFile) | 单独文件 |
| 加载方式 | 全局加载 | 按需加载 |
| 依赖声明 | /// <reference /> | import |
| 现代推荐度 | 较少使用 | 推荐使用 |
| 适用场景 | 声明文件、全局扩展 | 应用程序代码 |
 
==== 12.4.2 何时使用命名空间 ====
 
**推荐使用命名空间的场景:**
 
1. **声明文件(.d.ts)中组织类型**
2. **扩展现有库的全局类型**
3. **需要编译成单个文件的传统项目**
 
<code typescript>
// 声明文件中使用命名空间
// lodash.d.ts
declare namespace _ {
  interface LoDashStatic {
    chunk<T>(array: T[], size?: number): T[][];
    compact<T>(array: (T | null | undefined)[]): T[];
  }
}
 
declare const _: _.LoDashStatic;

推荐使用模块的场景:

1. 现代应用程序开发

2. 需要代码分割和懒加载

3. 使用打包工具(Webpack、Vite、Rollup)

4. Node.js 服务端开发

// 推荐使用模块方式
// utils/validation.ts
export interface Validator {
  isValid(value: unknown): boolean;
}
 
export class EmailValidator implements Validator {
  isValid(value: unknown): boolean {
    return typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
  }
}
 
// main.ts
import { EmailValidator, Validator } from './utils/validation';
 
const validator: Validator = new EmailValidator();

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

class Album {
  label!: Album.AlbumLabel;
}
 
namespace Album {
  export class AlbumLabel {
    constructor(public label: string) {}
  }
 
  // 静态属性
  export let defaultLabel = new AlbumLabel("Unknown");
 
  // 静态方法
  export function createWithLabel(title: string, labelName: string): Album {
    const album = new Album();
    album.label = new AlbumLabel(labelName);
    return album;
  }
}
 
// 使用
const album = new Album();
album.label = new Album.AlbumLabel("Sony Music");
 
const album2 = Album.createWithLabel("My Album", "Warner");

命名空间可以与函数合并,添加属性和方法:

function greet(name: string): string {
  return greet.prefix + name + greet.suffix;
}
 
namespace greet {
  export let prefix = "Hello, ";
  export let suffix = "!";
 
  export function formal(name: string): string {
    return `Dear ${name},`;
  }
 
  export function casual(name: string): string {
    return `Hey ${name}!`;
  }
}
 
// 使用
console.log(greet("World"));  // Hello, World!
greet.prefix = "Hi, ";
console.log(greet("World"));  // Hi, World!
console.log(greet.formal("Mr. Smith"));  // Dear Mr. Smith,
enum Color {
  Red,
  Green,
  Blue
}
 
namespace Color {
  export function toHex(color: Color): string {
    switch (color) {
      case Color.Red: return "#FF0000";
      case Color.Green: return "#00FF00";
      case Color.Blue: return "#0000FF";
      default: return "#000000";
    }
  }
 
  export function fromHex(hex: string): Color | undefined {
    switch (hex.toUpperCase()) {
      case "#FF0000": return Color.Red;
      case "#00FF00": return Color.Green;
      case "#0000FF": return Color.Blue;
      default: return undefined;
    }
  }
}
 
// 使用
console.log(Color.toHex(Color.Red));  // #FF0000
const color = Color.fromHex("#00FF00");
console.log(color);  // 1 (Color.Green)

命名空间常用于扩展全局对象(如 Window):

// global-extensions.d.ts
declare global {
  interface Window {
    myApp: {
      version: string;
      config: AppConfig;
    };
  }
 
  interface AppConfig {
    apiUrl: string;
    debug: boolean;
  }
}
 
// 使用时需要导出,使其成为模块
declare global {
  // 扩展声明
}
 
export {};  // 使文件成为模块
// 扩展 Array 接口
declare global {
  interface Array<T> {
    first(): T | undefined;
    last(): T | undefined;
  }
}
 
// 实现
if (!Array.prototype.first) {
  Array.prototype.first = function<T>(this: T[]): T | undefined {
    return this[0];
  };
}
 
if (!Array.prototype.last) {
  Array.prototype.last = function<T>(this: T[]): T | undefined {
    return this[this.length - 1];
  };
}
 
export {};
 
// 使用
const arr = [1, 2, 3];
console.log(arr.first());  // 1
console.log(arr.last());   // 3

本章我们学习了:

1. 命名空间基础 - 概念、定义、export 关键字

2. 嵌套命名空间 - 层级结构、import 别名

3. 多文件命名空间 - 跨文件组织、reference 指令

4. 与模块的区别 - 适用场景对比

5. 声明合并 - 与类、函数、枚举的合并

6. 全局扩展 - 扩展 Window、Array 等内置类型

创建一个 Geometry 命名空间,包含: - 计算圆面积的函数(接收半径参数) - 计算矩形面积的函数(接收宽高参数) - 计算三角形面积的函数(接收底高参数) - 导出一个常量 PI

参考答案

namespace Geometry {
  export const PI = Math.PI;
 
  export function circleArea(radius: number): number {
    return PI * radius * radius;
  }
 
  export function rectangleArea(width: number, height: number): number {
    return width * height;
  }
 
  export function triangleArea(base: number, height: number): number {
    return 0.5 * base * height;
  }
}
 
// 使用
console.log(Geometry.PI);  // 3.14159...
console.log(Geometry.circleArea(5));  // 78.54...
console.log(Geometry.rectangleArea(4, 6));  // 24
console.log(Geometry.triangleArea(4, 6));   // 12

创建一个 Company 命名空间,包含: - HR 子命名空间:Employee 类、Department 枚举 - Finance 子命名空间:Invoice 类、calculateTax 函数 - 使用 import 别名简化访问

参考答案

namespace Company {
  export namespace HR {
    export enum Department {
      Engineering = "Engineering",
      Sales = "Sales",
      Marketing = "Marketing"
    }
 
    export class Employee {
      constructor(
        public name: string,
        public department: Department,
        public salary: number
      ) {}
 
      getInfo(): string {
        return `${this.name} - ${this.department}`;
      }
    }
  }
 
  export namespace Finance {
    export class Invoice {
      constructor(
        public id: string,
        public amount: number,
        public taxRate: number = 0.1
      ) {}
 
      getTotal(): number {
        return this.amount * (1 + this.taxRate);
      }
    }
 
    export function calculateTax(amount: number, rate: number): number {
      return amount * rate;
    }
  }
}
 
// 使用别名
import HR = Company.HR;
import Finance = Company.Finance;
 
const emp = new HR.Employee("Alice", HR.Department.Engineering, 5000);
console.log(emp.getInfo());
 
const invoice = new Finance.Invoice("INV001", 1000, 0.13);
console.log(invoice.getTotal());
console.log(Finance.calculateTax(1000, 0.13));

创建一个 Calculator 类与命名空间合并: - Calculator 类具有基本运算方法 - 命名空间部分添加历史记录功能 - 命名空间部分添加常量(如 PI、E)

参考答案

class Calculator {
  add(a: number, b: number): number {
    Calculator.addToHistory(`${a} + ${b} = ${a + b}`);
    return a + b;
  }
 
  subtract(a: number, b: number): number {
    Calculator.addToHistory(`${a} - ${b} = ${a - b}`);
    return a - b;
  }
 
  multiply(a: number, b: number): number {
    Calculator.addToHistory(`${a} * ${b} = ${a * b}`);
    return a * b;
  }
 
  divide(a: number, b: number): number {
    if (b === 0) throw new Error("Cannot divide by zero");
    Calculator.addToHistory(`${a} / ${b} = ${a / b}`);
    return a / b;
  }
}
 
namespace Calculator {
  // 常量
  export const PI = Math.PI;
  export const E = Math.E;
 
  // 历史记录
  const history: string[] = [];
 
  export function addToHistory(entry: string): void {
    history.push(entry);
  }
 
  export function getHistory(): string[] {
    return [...history];
  }
 
  export function clearHistory(): void {
    history.length = 0;
  }
 
  // 工具函数
  export function factorial(n: number): number {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
  }
}
 
// 使用
const calc = new Calculator();
console.log(calc.add(5, 3));       // 8
console.log(calc.multiply(4, 7));  // 28
console.log(Calculator.PI);        // 3.14159...
console.log(Calculator.factorial(5));  // 120
console.log(Calculator.getHistory());
// ["5 + 3 = 8", "4 * 7 = 28"]

扩展 String 接口,添加以下方法: - reverse(): 反转字符串 - isPalindrome(): 判断是否为回文 - toTitleCase(): 转换为标题格式

参考答案

// string-extensions.d.ts
declare global {
  interface String {
    reverse(): string;
    isPalindrome(): boolean;
    toTitleCase(): string;
  }
}
 
// string-extensions.ts
if (!String.prototype.reverse) {
  String.prototype.reverse = function(this: string): string {
    return this.split('').reverse().join('');
  };
}
 
if (!String.prototype.isPalindrome) {
  String.prototype.isPalindrome = function(this: string): boolean {
    const cleaned = this.toLowerCase().replace(/[^a-z0-9]/g, '');
    return cleaned === cleaned.reverse();
  };
}
 
if (!String.prototype.toTitleCase) {
  String.prototype.toTitleCase = function(this: string): string {
    return this.replace(/\w\S*/g, (txt) => 
      txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
    );
  };
}
 
export {};
 
// 使用
const str = "hello world";
console.log(str.reverse());        // dlrow olleh
console.log("A man a plan a canal Panama".isPalindrome());  // true
console.log("hello world".toTitleCase());  // Hello World

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • typescript/第十二章_命名空间.txt
  • 最后更改: 2026/03/09 15:25
  • 张叶安