第十二章:命名空间
本章概述
命名空间(Namespace)是 TypeScript 早期版本中用于组织代码和组织类型命名冲突的重要特性。虽然在 ES6 模块普及后,模块已成为首选的代码组织方式,但命名空间在某些场景下仍然非常有用,特别是在声明文件和全局扩展中。本章将详细介绍命名空间的概念、用法以及与模块的区别。
12.1 什么是命名空间
12.1.1 命名空间的概念
命名空间是一种将相关代码组织在一起的方式,它可以包含类、接口、函数、变量等。命名空间通过创建一个新的作用域来避免命名冲突。
// 定义命名空间
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"));
12.1.2 为什么需要命名空间
在没有模块系统(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({});
12.2 命名空间的基本用法
12.2.1 定义命名空间
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
12.2.2 嵌套命名空间
命名空间可以嵌套定义,形成层级结构:
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"
});
12.2.3 别名简化访问
对于深层嵌套的命名空间,可以使用 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");
12.3 命名空间与文件
12.3.1 单文件中的多个命名空间
一个文件可以包含多个命名空间:
// 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
12.3.2 跨文件的命名空间
命名空间可以分散在多个文件中,通过 “ <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);
}
}
}
12.3.3 编译配置
编译多文件命名空间需要特殊配置:
{
"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;
12.4.3 何时使用模块
推荐使用模块的场景:
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();
12.5 命名空间的高级用法
12.5.1 命名空间与类合并
命名空间可以与类合并,用于添加静态成员:
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");
12.5.2 命名空间与函数合并
命名空间可以与函数合并,添加属性和方法:
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,
12.5.3 命名空间与枚举合并
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)
12.6 全局扩展
12.6.1 扩展全局对象
命名空间常用于扩展全局对象(如 Window):
// global-extensions.d.ts
declare global {
interface Window {
myApp: {
version: string;
config: AppConfig;
};
}
interface AppConfig {
apiUrl: string;
debug: boolean;
}
}
// 使用时需要导出,使其成为模块
declare global {
// 扩展声明
}
export {}; // 使文件成为模块
12.6.2 扩展内置类型
// 扩展 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
12.7 本章小结
本章我们学习了:
1. 命名空间基础 - 概念、定义、export 关键字
2. 嵌套命名空间 - 层级结构、import 别名
3. 多文件命名空间 - 跨文件组织、reference 指令
4. 与模块的区别 - 适用场景对比
5. 声明合并 - 与类、函数、枚举的合并
6. 全局扩展 - 扩展 Window、Array 等内置类型
12.8 练习题
练习 1:命名空间基础
创建一个 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
练习 2:嵌套命名空间
创建一个 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));
练习 3:声明合并
创建一个 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"]
练习 4:全局扩展
扩展 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