命名空间

命名空间是TypeScript早期版本中用于模块化的主要方式,在ES6模块系统普及之前被广泛使用。TypeScript中的命名空间(Namespace)是一种组织代码的方式,它可以将相关的代码封装在一个命名空间内,避免与全局作用域的命名冲突。

命名空间的基本用法

我们可以使用namespace关键字定义一个命名空间,命名空间内部的成员默认是私有的,需要使用export关键字导出才能在外部访问。

namespace MathUtils {
  const PI = 3.14159; // 私有成员,外部无法访问

  export function add(a: number, b: number): number {
    return a + b;
  }

  export function multiply(a: number, b: number): number {
    return a * b;
  }

  export function circleArea(radius: number): number {
    return PI * radius * radius;
  }
}

// 使用命名空间中的函数
console.log(MathUtils.add(1, 2)); // 输出: 3
console.log(MathUtils.multiply(3, 4)); // 输出: 12
console.log(MathUtils.circleArea(5)); // 输出: 78.53975

代码中,我们定义了MathUtils命名空间,其中包含了一个私有常量PI和三个导出的函数。外部代码可以通过MathUtils.add()的形式访问导出的成员,但无法访问未导出的PI常量。

命名空间中导出类和接口

命名空间不仅可以导出函数,还可以导出类、接口、类型别名等各种TypeScript结构。

namespace Shapes {
  export interface Point {
    x: number;
    y: number;
  }

  export class Circle {
    constructor(public center: Point, public radius: number) {}

    area(): number {
      return Math.PI * this.radius ** 2;
    }
  }

  export class Rectangle {
    constructor(public topLeft: Point, public width: number, public height: number) {}

    area(): number {
      return this.width * this.height;
    }
  }
}

// 使用命名空间中的类型和类
const origin: Shapes.Point = { x: 0, y: 0 };
const circle = new Shapes.Circle(origin, 10);
const rect = new Shapes.Rectangle({ x: 5, y: 5 }, 20, 30);

console.log(circle.area()); // 输出: 314.159...
console.log(rect.area()); // 输出: 600

嵌套命名空间

命名空间可以嵌套定义,用于创建更加层次化的代码组织结构。

namespace App {
  export namespace Models {
    export interface User {
      id: number;
      name: string;
      email: string;
    }

    export interface Product {
      id: number;
      name: string;
      price: number;
    }
  }

  export namespace Services {
    export function getUser(id: number): Models.User {
      // 模拟获取用户数据
      return { id, name: "张三", email: "zhangsan@example.com" };
    }

    export function getProduct(id: number): Models.Product {
      // 模拟获取产品数据
      return { id, name: "商品A", price: 99.99 };
    }
  }
}

// 使用嵌套命名空间
const user: App.Models.User = App.Services.getUser(1);
const product: App.Models.Product = App.Services.getProduct(1);

命名空间别名

当命名空间嵌套层次较深时,使用完整路径访问成员会比较繁琐。我们可以使用import关键字为命名空间创建别名。

namespace Company {
  export namespace Department {
    export namespace Engineering {
      export class Developer {
        constructor(public name: string) {}

        code(): void {
          console.log(`${this.name} is coding...`);
        }
      }
    }
  }
}

// 创建命名空间别名
import Engineering = Company.Department.Engineering;

// 使用别名访问
const dev = new Engineering.Developer("Tom");
dev.code(); // 输出: Tom is coding...

注意这里的import不是ES6模块的导入语法,而是TypeScript命名空间的别名语法。

跨文件的命名空间

命名空间可以跨多个文件定义,TypeScript会自动将同名的命名空间合并。

shapes.ts

namespace Shapes {
  export class Triangle {
    constructor(public base: number, public height: number) {}

    area(): number {
      return (this.base * this.height) / 2;
    }
  }
}

circles.ts

/// <reference path="shapes.ts" />

namespace Shapes {
  export class Circle {
    constructor(public radius: number) {}

    area(): number {
      return Math.PI * this.radius ** 2;
    }
  }
}

main.ts

/// <reference path="shapes.ts" />
/// <reference path="circles.ts" />

const triangle = new Shapes.Triangle(10, 5);
const circle = new Shapes.Circle(7);

console.log(triangle.area()); // 输出: 25
console.log(circle.area()); // 输出: 153.938...

代码中,我们使用/// <reference path="..." />三斜线指令来引用其他文件,这样TypeScript编译器就知道文件之间的依赖关系。跨文件定义的同名命名空间会被自动合并,最终Shapes命名空间包含了TriangleCircle两个类。

使用三斜线指令时,需要注意编译配置。如果使用tsc直接编译,可以通过--outFile参数将多个文件合并输出。

tsc --outFile dist/bundle.js main.ts

命名空间与模块的区别

命名空间和ES6模块都可以用于组织代码,但它们有本质区别。

特性 命名空间 ES6模块
作用域 全局作用域内的逻辑分组 文件级别的物理隔离
依赖声明 三斜线指令 import/export语句
加载方式 通常合并为单文件 支持按需加载
使用场景 传统脚本、全局库声明 现代模块化开发

在现代TypeScript项目中,推荐使用ES6模块系统进行代码组织,现在命名空间主要用于以下场景:

  1. 为全局JavaScript库编写类型声明文件
  2. 在不支持模块系统的环境中组织代码
  3. 将相关类型定义分组(配合模块使用)

命名空间在声明文件中的应用

命名空间在.d.ts声明文件中仍然非常有用,特别是为没有模块化的第三方JavaScript库编写类型声明时,这需要配合declare关键字使用。declare用于声明变量、函数、类、命名空间、模块等的类型信息,但不生成任何实际的JavaScript代码,它的主要作用是告诉TypeScript编译器,这个东西在运行时是存在的,按给定类型来检查它。下面是一个例子。

jquery.d.ts

declare namespace JQuery {
  interface AjaxSettings {
    url?: string;
    method?: string;
    data?: any;
    success?: (data: any) => void;
    error?: (error: any) => void;
  }

  interface JQueryStatic {
    ajax(settings: AjaxSettings): void;
    (selector: string): JQueryObject;
  }

  interface JQueryObject {
    html(content?: string): JQueryObject | string;
    css(property: string, value?: string): JQueryObject | string;
    on(event: string, handler: (e: Event) => void): JQueryObject;
  }
}

declare const $: JQuery.JQueryStatic;

上面的声明文件为jQuery库提供了类型支持,使用命名空间将相关的接口定义组织在了一起。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。