新项目一律用 ES6 模块(import/export),命名空间仅用于.d.ts声明、适配全局库或单文件构建;ES6模块天然作用域隔离、支持tree-shaking,而namespace共享全局环境、依赖隐式引用,易导致耦合与维护问题。

直接说结论:新项目一律用 ES6 模块(import/export),命名空间(namespace)只在特定场景下保留使用,比如写 .d.ts 声明文件、适配老式全局库,或极少数需要多文件合并到单个 JS 文件的构建场景。
ES6 模块的作用域隔离机制
每个含顶层 import 或 export 的文件,自动成为独立模块。模块内声明的变量、函数、类型默认仅在本文件作用域可见——不加 export 就完全对外不可见,天然隔离。
- 模块之间靠显式导入导出建立依赖关系,编译器能准确追踪依赖图
- 支持 tree-shaking(摇树优化),未被 import 的导出内容可被移除
- 运行时由模块加载器(如 Node.js 的 ESM、Webpack、Vite)处理解析和执行顺序
- 类型和值可共用同一标识符(例如
export interface Foo和export const Foo = ...可同时存在,TS 会自动区分)
命名空间的作用域隔离机制
namespace 不是独立作用域,而是生成一个全局对象,把 export 成员挂在其属性上。它本质是 IIFE 封装后赋值给全局变量,所有命名空间共享同一个全局环境。
- 多个
namespace X { }声明会合并到同一个X对象(声明合并),不是覆盖而是累加 - 没加
export的成员仅在该namespace内部可见,但加了export就暴露为X.Y,仍属于全局命名空间 - 无法静态分析依赖,容易造成隐式耦合;需手动用
/// <reference path="..."></reference>声明引用关系 - 编译后生成的是普通 JS 对象赋值语句,不依赖模块系统,适合纯 script 标签引入的前端小项目
什么时候该用命名空间
不是“选不选”,而是“能不能不用”。目前只有三类合理场景:
-
.d.ts文件中描述第三方库的全局结构(例如declare namespace jQuery) - 对接没有模块系统的旧 JS 库(如某些插件挂载在
window.MyLib上),用namespace MyLib做类型补全 - 极简构建流程中需将多个 TS 文件打包成单个 JS(配合
tsc --outFile),且不引入模块加载器
常见误用和坑点
这些做法现在都应避免:
- 在普通
.ts文件里用namespace组织业务逻辑(易污染全局、难维护、IDE 支持弱) - 用
/// <reference path="xxx.ts"></reference>引入模块文件(应该改用import) - 混用:一个文件既写
export又写namespace,导致类型和值作用域混乱 - 以为
namespace能替代模块做代码拆分——它不解决依赖管理,只解决命名冲突


















