阮一峰《TypeScript 教程》学习笔记——namespace
1. 一段话总结
TypeScript 的 namespace(命名空间) 是 ES 模块诞生前用于组织相关代码的容器化方案,核心通过 namespace 关键字包裹代码,内部成员需加 export 才能对外暴露(否则仅内部可见);支持嵌套、为外部成员起别名(用内部 import 命令),且 同名 namespace 会自动合并,还可与同名函数、class、enum 合并(需先声明函数/class/enum,确保先创建基础对象);编译后会转为 JavaScript 对象(非纯类型代码,保留运行时值);因 ES 模块是 JavaScript 标准语法、无需编译转换且更简洁,官方 不推荐使用 namespace,建议优先用 ES 模块替代。
2. 思维导图

3. 详细总结
一、namespace 概述
- 定义:TypeScript 早期设计的代码组织方案,用于将相关变量、函数、类型(接口、class)封装在一个容器中,避免全局作用域污染,中文译为“命名空间”。
- 核心定位:ES 模块诞生前的过渡方案,目前因 ES 模块成为 JavaScript 标准,官方已不推荐使用。
- 编译特性:会转为 JavaScript 对象(非纯类型代码),保留运行时值,如:
// TS 代码 namespace Utility { export function log(msg:string) { console.log(msg); } } // 编译后 JS 代码 var Utility; (function (Utility) {function log(msg) { console.log(msg); }Utility.log = log; })(Utility || (Utility = {}));
二、namespace 基本用法
1. 定义与成员暴露
- 语法结构:用
namespace 名称 {}包裹代码,内部成员需加export关键字才能对外暴露,未加export的成员仅内部可见。 - 示例:
namespace Utils {// 未暴露:仅内部可用function isNumber(value:any) { return typeof value === 'number'; }// 暴露:外部可访问export function isString(value:any) { return typeof value === 'string'; } } Utils.isString('yes'); // 正确 Utils.isNumber(123); // 报错(未暴露)
2. 内部引用外部成员(别名)
- 作用:当外部成员名称较长时,用
import 别名 = 外部成员简化引用。 - 适用场景:内部引用其他 namespace 的暴露成员,或外部引用嵌套 namespace。
- 示例:
// 场景1:内部引用外部 namespace 成员 namespace App {import isStr = Utils.isString; // 别名isStr('hello'); // 等同于 Utils.isString('hello') } // 场景2:外部引用嵌套 namespace namespace Shapes {export namespace Polygons { export class Square {} } } import polygons = Shapes.Polygons; let sq = new polygons.Square(); // 简化 Shapes.Polygons.Square()
3. 嵌套命名空间
- 语法:namespace 内部可嵌套另一个 namespace,嵌套的 namespace 需加
export才能对外暴露。 - 引用规则:必须从最外层 namespace 开始逐层引用,不能跳过外层直接访问内层。
- 示例:
namespace Utils {export namespace Messaging { // 加 export 暴露嵌套命名空间export function log(msg:string) { console.log(msg); }} } Utils.Messaging.log('hi'); // 正确(从外层开始引用) Messaging.log('hi'); // 报错(跳过外层)
三、namespace 的输出与导入
当 namespace 定义在单独文件中时,需通过 export 输出,并在外部文件用 import 导入:
| 操作类型 | 语法示例 | 说明 |
|---|---|---|
| 文件内输出 | // shapes.ts export namespace Shapes { export class Triangle {} } | 用 export 对外输出整个 namespace |
| 外部导入(写法1) | import { Shapes } from './shapes'; let t = new Shapes.Triangle(); | 直接导入命名空间 |
| 外部导入(写法2) | import * as shapes from './shapes'; let t = new shapes.Shapes.Triangle(); | 用别名导入整个文件,再访问命名空间 |
官方建议:此场景更适合用 ES 模块直接输出 class,而非嵌套 namespace(见下文对比)。
四、namespace 的合并
多个同名 namespace 或 namespace 与其他结构(函数、class、enum)会自动合并,是 namespace 的核心特性之一:
1. 合并类型及规则
| 合并对象 | 合并条件 | 示例代码 | 注意事项 |
|---|---|---|---|
| 同名 namespace | 无额外条件,自动合并 | namespace A { export class X {} } namespace A { export interface Y {} } // 合并后:A 包含 X 和 Y | 非 export 成员仅在原 namespace 内可用,不共享 |
| namespace + 函数 | 需先声明函数,再定义 namespace | function f() { return f.version; } namespace f { export const version = '1.0'; } f(); // '1.0' | 命名空间为函数对象添加属性 |
| namespace + class | 需先声明 class,再定义 namespace | class C { foo = 1; } namespace C { export const bar = 2; } C.bar; // 2 | 命名空间为类添加静态属性 |
| namespace + enum | 需先声明 enum,再定义 namespace | enum E { A, B } namespace E { export function log() { console.log(E.A); } } E.log(); // 0 | enum 成员与 namespace 暴露成员不可同名,否则报错 |
2. 合并核心规则
- 非
export成员仅在其定义的 namespace 内可用,合并后不共享; - 与函数、class、enum 合并时,需先声明基础结构(函数/class/enum),确保先创建对象,再通过 namespace 添加属性/方法;
- enum 与 namespace 合并时,成员名称必须唯一,否则触发编译错误。
五、namespace 与 ES 模块的对比
因 ES 模块是 JavaScript 官方标准,namespace 已被替代,核心差异如下:
| 对比维度 | namespace | ES 模块 |
|---|---|---|
| 文件内数量 | 1个文件可定义多个 namespace | 1个文件对应1个模块(含 import/export 即为模块) |
| 编译产物 | 转为 JavaScript 对象(保留运行值,非标准语法) | 保留 ES 模块标准语法(import/export),无需额外转换 |
| 语法标准 | TypeScript 私有语法,兼容性受限 | JavaScript 官方标准,浏览器/Node.js 原生支持 |
| 代码简洁性 | 嵌套引用繁琐(如 A.B.C) | 直接输出/导入成员(如 import { X } from './a') |
官方推荐方案:用 ES 模块替代 namespace,示例:
// shapes.ts(ES 模块) export class Triangle {} export class Square {} // 外部使用 import * as shapes from './shapes'; let t = new shapes.Triangle();
六、官方建议
- 优先使用 ES 模块:因 ES 模块是标准语法、无需编译转换、兼容性更好,仅在维护旧的 TypeScript 代码时使用 namespace;
- 避免过度嵌套:namespace 嵌套会导致引用繁琐,ES 模块可通过文件目录组织代码,更清晰。
4. 关键问题
问题1:TypeScript 中 namespace 与 ES 模块的核心差异是什么?为何官方推荐用 ES 模块替代 namespace?
答案:
两者核心差异体现在语法标准、编译产物、使用灵活性三方面:
- 语法标准:namespace 是 TypeScript 私有语法,ES 模块是 JavaScript 官方标准,浏览器/Node.js 原生支持,无需额外适配;
- 编译产物:namespace 编译后转为 JavaScript 对象(非标准语法,保留运行值),ES 模块保留标准
import/export语法,无冗余转换; - 使用灵活性:1个文件可定义多个 namespace,嵌套引用繁琐(如
A.B.C);ES 模块1个文件对应1个模块,可通过文件目录组织代码,直接导入所需成员(如import { X } from './a')。
官方推荐 ES 模块的核心原因是:ES 模块符合 JavaScript 语言发展方向,兼容性更广、代码更简洁,且能完全覆盖 namespace 的代码组织需求,无需依赖 TS 私有语法。
问题2:namespace 支持与哪些结构合并?合并时有哪些必须遵守的关键规则?
答案:
namespace 支持与 同名 namespace、函数、class、enum 四类结构合并,关键规则如下:
-
同名 namespace 合并:
- 规则:自动合并,无需额外条件;非
export成员仅在原 namespace 内可用,不共享; - 示例:
namespace A { export class X {} }与namespace A { export interface Y {} }合并后,A同时包含X和Y。
- 规则:自动合并,无需额外条件;非
-
与函数/class/enum 合并:
- 核心规则:必须先声明函数/class/enum,再定义同名 namespace(确保先创建基础对象,namespace 为其添加属性/方法);
- 细分规则:
- 与函数合并:namespace 为函数对象添加属性(如
function f() {}+namespace f { export const v = 1 },可通过f.v访问); - 与 class 合并:namespace 为类添加静态属性(如
class C {}+namespace C { export const v = 1 },可通过C.v访问); - 与 enum 合并:namespace 为 enum 添加方法,但 enum 成员与 namespace 暴露成员不可同名(否则报错)。
- 与函数合并:namespace 为函数对象添加属性(如
问题3:如何在 namespace 内部暴露成员供外部使用?如何在 namespace 内部引用外部成员?请结合示例说明。
答案:
(1)内部成员对外暴露:用 export 关键字
- 规则:namespace 内部成员需加
export才能对外可见,未加export的成员仅内部可用; - 示例:
namespace MathUtils {// 未暴露:仅内部可用function formatNum(n:number) { return n.toFixed(2); }// 暴露:外部可访问export function add(a:number, b:number) {return formatNum(a + b); // 内部可调用未暴露成员} } MathUtils.add(1.234, 5.678); // 6.91(正确,暴露成员) MathUtils.formatNum(1.234); // 报错(未暴露成员)
(2)内部引用外部成员:用 import 别名 = 外部成员 语法
- 作用:简化外部成员的长名称引用,仅在当前 namespace 内生效;
- 示例:
// 外部 namespace namespace ExternalUtils {export function calculateArea(r:number) { return Math.PI * r ** 2; } }// 内部引用 namespace App {// 为 ExternalUtils.calculateArea 起别名 areaCalcimport areaCalc = ExternalUtils.calculateArea;// 用别名调用外部成员const circleArea = areaCalc(5); // 78.5398... }
