模块系统与命名空间
2026/7/9大约 5 分钟
模块系统与命名空间
概述
Go 的模块系统基于 package + 首字母大小写控制可见性,简单而一致。TypeScript 则继承自 JavaScript 的 ES Module(ESM)体系,同时保留了历史遗留的 namespace。对于 Go 开发者,ESM 的 export/import 机制并不难理解,但需要适应"每个文件都是模块"的哲学,以及 d.ts 类型声明文件的概念。
Go 开发者已知
// Go 模块 —— package 级别
package user
// 首字母大写 = 导出
type User struct {
Name string // 导出
email string // 未导出
}
func NewUser(name string) *User { // 导出
return &User{Name: name}
}
func validate(u *User) error { // 未导出
return nil
}
// 导入
// import "example.com/project/user"
// u := user.NewUser("Alice")Go 模块特点:
- Package 是组织单位,以文件夹为单位
- 首字母大小写控制导出/未导出
- 没有
export关键字 - 没有循环依赖(编译器严格禁止)
- 没有动态导入
- 没有类型声明文件的概念
TypeScript 怎么做
命名导出与导入
// user.ts —— 命名导出
export interface User {
name: string
email: string
}
export function createUser(name: string, email: string): User {
return { name, email }
}
export const DEFAULT_USER: User = { name: 'Guest', email: 'guest@example.com' }// app.ts —— 命名导入
import { User, createUser, DEFAULT_USER } from './user'
const user = createUser('Alice', 'alice@example.com')默认导出与导入
// logger.ts
export default class Logger {
log(msg: string): void {
console.log(msg)
}
}// app.ts
import Logger from './logger'
// 或同时导入默认和命名
// import Logger, { LogLevel } from './logger'默认导出 vs 命名导出
默认导出在重构时不如命名导出友好(重命名无需工具),且对 tree-shaking 不友好。优先命名导出。
// 推荐:命名导出
export const foo = () => {}
// 不推荐:默认导出
export default foo重导出与 Barrel 模式
// barrel 文件 —— index.ts
export { User, createUser } from './user'
export { authenticate, authorize } from './auth'
export { validateEmail, validatePassword } from './validators'
// 外部使用者只需:
// import { User, authenticate, validateEmail } from './services'动态 import()
// 代码分割 / 按需加载
async function loadAdminModule() {
const admin = await import('./admin')
const users = await admin.loadUsers()
return users
}
// 条件加载
if (process.env.NODE_ENV === 'production') {
const { track } = await import('./analytics')
track('pageview')
}namespace(了解即可)
// namespace —— 历史上用于组织代码(现在基本被 ESM 取代)
namespace Geometry {
export interface Point {
x: number
y: number
}
export function distance(a: Point, b: Point): number {
return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)
}
export namespace Polygon {
export function area(points: Point[]): number {
// ...
}
}
}
// 使用
const p: Geometry.Point = { x: 0, y: 0 }
Geometry.distance(p, { x: 1, y: 1 })d.ts 类型声明文件
// types.d.ts —— 声明模块类型
declare module 'some-lib' {
export function doSomething(): void
export const VERSION: string
}
// global.d.ts —— 声明全局类型
declare global {
interface Window {
__APP_CONFIG__: {
apiUrl: string
debug: boolean
}
}
}差异分析
| 维度 | Go | TypeScript |
|---|---|---|
| 模块单位 | package(文件夹) | 每个 .ts 文件 |
| 导出控制 | 首字母大小写 | export 关键字 |
| 默认导出 | 无(都是命名) | export default |
| 重导出 | 不支持 | export { X } from './y' |
| 动态导入 | 不支持 | import('./module') |
| 可见性 | package 级(首字母小写) | 文件级(未 export 即私有) |
| 循环依赖 | 编译器禁止 | 支持(但应避免) |
| 类型声明 | 无 | .d.ts 文件 |
| 命名空间 | 无(package 级) | namespace(历史遗留) |
循环依赖:Go vs TypeScript
Go 编译器严格禁止循环依赖,这是 Go 设计的核心原则之一。
TypeScript/JavaScript 允许循环依赖,但会导致难以调试的问题:
// a.ts
import { B } from './b'
export const A = { name: 'A', dep: B }
// b.ts
import { A } from './a'
export const B = { name: 'B', dep: A }
// 运行时 A 的 dep 是 undefined!最佳实践: 像 Go 一样避免循环依赖。如果出现,提取公共依赖到一个新文件。
Bad Practice
// Bad: namespace 与 ESM 混用
export namespace Utils { // namespace 在现代 TS 中已不必要
export function format() {}
}
// 应该直接用模块级导出
export function format() {}
// Bad: 默认导出 + 命名导出混用过度
export default class Service {}
export { Service } // 重复导出
// Bad: 过度使用 Barrel 模式导致循环引用
// services/index.ts
export * from './user-service'
export * from './auth-service'
export * from './order-service' // 可能引入循环
// Bad: 内部模块细节被意外导出
// 应该只导出公共 API,而非所有内部函数Best Practice
// Good: 导出原则 —— 显式命名导出
// user.service.ts
export interface User {
id: string
name: string
}
export function createUser(name: string): User {
return { id: crypto.randomUUID(), name }
}
// 文件作用域内私有
function generateId(): string {
return crypto.randomUUID()
}
// Good: Barrel 模式 —— 明确的 public API
// services/index.ts
export { User, createUser } from './user.service'
export { AuthService, authenticate } from './auth.service'
// 不要 export * from '...'
// Good: 按功能组织模块(与 Go 的 package 理念类似)
// features/
// users/
// user.service.ts —— 业务逻辑
// user.controller.ts —— 路由/请求处理
// user.types.ts —— 类型定义
// user.test.ts —— 测试
// index.ts —— barrel
// Good: 使用 type import 减少运行时开销
import type { User } from './user.types'
// 编译后 User 类型被移除,不产生运行时依赖
// Good: 路径别名
// tsconfig.json:
// {
// "compilerOptions": {
// "paths": {
// "@app/*": ["./src/*"],
// "@shared/*": ["./src/shared/*"]
// }
// }
// }
import { User } from '@app/models/user'type import 优化
// 运行时无开销的导入
import type { User, Config } from './types'
import { createUser } from './service'
// TS 5.0+ 也支持:
// import { type User, createUser } from './module'当您只需要类型而不需要值时,使用 import type 确保编译后不生成任何 JavaScript 代码。
code-tabs 对比
总结
| Go 概念 | TypeScript 对应 |
|---|---|
| package | 目录/文件 + ESM export |
| 首字母大写导出 | export 关键字 |
| 首字母小写私有 | 未 export 的声明 |
| 无对应 | 默认导出 export default |
| 无对应 | d.ts 类型声明文件 |
| 无对应 | 动态 import() |
| 无对应 | Barrel 模式 |
核心原则:始终使用 ES Module(export/import),避免 namespace,默认导出仅在有明确理由时使用。TypeScript 的模块系统比 Go 更加灵活,但需要开发者自律以保持整洁。
下一章将介绍 异步编程:Promise 与 async/await。