命名空间
命名空间是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命名空间包含了Triangle和Circle两个类。
使用三斜线指令时,需要注意编译配置。如果使用tsc直接编译,可以通过--outFile参数将多个文件合并输出。
tsc --outFile dist/bundle.js main.ts
命名空间与模块的区别
命名空间和ES6模块都可以用于组织代码,但它们有本质区别。
| 特性 | 命名空间 | ES6模块 |
|---|---|---|
| 作用域 | 全局作用域内的逻辑分组 | 文件级别的物理隔离 |
| 依赖声明 | 三斜线指令 | import/export语句 |
| 加载方式 | 通常合并为单文件 | 支持按需加载 |
| 使用场景 | 传统脚本、全局库声明 | 现代模块化开发 |
在现代TypeScript项目中,推荐使用ES6模块系统进行代码组织,现在命名空间主要用于以下场景:
- 为全局JavaScript库编写类型声明文件
- 在不支持模块系统的环境中组织代码
- 将相关类型定义分组(配合模块使用)
命名空间在声明文件中的应用
命名空间在.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库提供了类型支持,使用命名空间将相关的接口定义组织在了一起。