| 涵盖:导入导出、执行机制、跨平台设计


一、模块基础:importexport

1. 导出(export

类型 语法 特点
命名导出 export const name = 'Alice';
export function greet() {}
export { name, greet };
可多个,导入时名字必须匹配
默认导出 export default function() {}
export default obj;
每模块最多一个,导入时可自定义名字

2. 导入(import

场景 语法 说明
导入命名导出 import { name, greet } from './utils.js'; 名字必须匹配
导入默认导出 import main from './utils.js'; 名字可自定义
同时导入 import main, { version } from './utils.js'; 默认 + 命名
全部导入 import * as utils from './utils.js'; utils 是命名空间对象
只执行(副作用) import './polyfill.js'; 不导入值,只执行代码

二、模块的执行机制

✅ 模块只会执行一次(单例)

  • 第一次 import执行文件代码(顶层代码)。
  • 后续 import不执行,直接从模块缓存中取结果。
  • 所有导入者共享同一个状态(如 let count = 0)。
1
2
3
// utils.js
let count = 0;
export const inc = () => count++;
1
// a.js 和 b.js 都 import inc → 共享同一个 count

🔗 import 的本质:创建“实时指针”(Live Binding)

  • import 不是“拷贝值”,而是创建一个指向目标变量的指针
  • 即使变量后续改变,所有导入者都能看到最新值。
1
2
3
4
5
6
7
8
9
// counter.js
export let count = 0;
export const inc = () => count++;

// a.js
import { count } from "./counter.js";
console.log(count); // 0
inc();
console.log(count); // 1 ← 自动更新!

底层机制简述

当你写:

1
import { greet } from "./hello.js";

浏览器或打包工具会:

  • 解析模块路径:找到 hello.js
  • 下载文件(如果是浏览器)
  • 编译并执行模块(只执行一次)
  • 建立模块记录(Module Record):记录导出了哪些绑定
  • 链接导入:把 greet 映射到 hello.js 中的 greet 变量(引用)
  • 执行代码

整个过程是模块化的、隔离的、可预测的


三、模块加载流程(4 阶段)

阶段 说明
1. 解析 静态分析 import/export,构建依赖图,验证语法
2. 加载 下载模块文件(网络/文件系统)
3. 链接 创建“模块环境记录”,建立“实时绑定”(指针)
4. 执行 执行顶层代码,初始化 export 变量

⚠️ 顺序:深度优先,依赖项先执行。


四、重新导出(Re-exporting)

语法 作用 是否包含 default
export * from 'x.js' 透传 x.js 的所有命名导出 ❌ 否
export { name } from 'x.js' 只导出 x.jsname ❌ 否
export { default } from 'x.js' 透传 default 导出 ✅ 是
export { name as newName } from 'x.js' 重命名导出 ✅ 可重命名

🎯 用途:创建聚合模块(如 index.js)。


五、default vs 命名导出

问题 答案
export default 能用 {} 接收吗? ❌ 不能!必须用 import xxx from
命名导出能用 import xxx from 接收吗? ❌ 不能!必须用 import { xxx } from
如何同时导入? import defaultName, { named } from 'module'
default 能重命名吗? import { default as main } from 'module'

六、模块的跨平台挑战与解决方案

❗ 问题:环境差异

环境 特有全局变量
浏览器 window, document, localStorage
Node.js process, require, __dirname

❌ 模块若依赖 window,在 Node.js 中会报错。


✅ 解决方案 1:分层架构 —— “核心 + 绑定”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+------------------+
| 应用层 |
+--------+---------+
|
+--------v---------+
| 绑定层 (Bindings)|
| - browser.js |
| - node.js |
+--------+---------+
|
+--------v---------+
| 核心层 (Core) |
| - 纯逻辑:hash, validate |
| - 无环境依赖 |
+------------------+

✅ 优点

  • 核心可无限复用。
  • 绑定轻量、可替换。
  • 易测试、易维护。

✅ 解决方案 2:运行时检测(Feature Detection)

1
2
3
4
5
6
7
8
let password;
if (typeof process !== "undefined" && process.env.PASSWORD) {
password = process.env.PASSWORD; // Node.js
} else if (typeof document !== "undefined") {
password = document.getElementById("password")?.value; // 浏览器
} else {
throw new Error("No password source");
}

🔑 原则:检测功能,不检测环境


✅ 解决方案 3:Polyfill + 动态导入

1
2
3
4
// 确保 fetch 在所有环境可用
if (typeof fetch === "undefined") {
globalThis.fetch = (await import("node-fetch")).default;
}

🔑 使用 globalThis:在所有环境中都指向全局对象。


七、最佳实践清单

原则 建议
🧱 分离关注点 核心逻辑 vs I/O 操作
🔍 检测功能 typeof fetch === 'function' 而非 window
🌐 使用 globalThis 安全访问全局对象
📦 小模块,高内聚 一个模块只做一件事
🧪 测试核心 纯函数易单元测试
📚 文档说明 标注模块的环境依赖

🎯 总结口诀

“一缓存、二绑定、三核心、四检测”

  1. 一缓存:模块只执行一次,结果缓存共享。
  2. 二绑定import 是实时指针,不是值拷贝。
  3. 三核心:纯逻辑独立,不依赖环境。
  4. 四检测:用 typeof 检测功能,实现跨平台。