为什么需要模块化?从 script 标签说起
本节目标
- 理解「没有模块化」时全局变量互相污染的问题
- 看
script标签按顺序加载的脆弱性 - 建立「模块 = 独立作用域 + 明确依赖」的认知
早期 JS 把所有代码塞进几个 <script>,变量全是全局的。两个文件都定义 name,后面覆盖前面,bug 难以定位。
// 运行环境:浏览器(或 Node 16+ 用 var 演示,见下)
// 演示「全局污染」:两段脚本共享全局作用域
var name = 'A' // 脚本1
// ... 另一文件 ...
var name = 'B' // 脚本2 不小心覆盖了 name,且不会报错!
console.log(name) // 'B',但脚本1作者以为还是 'A'
// 用「函数作用域」隔离是最朴素的模块化雏形
function moduleUser() {
var name = 'A' // 现在 name 只在函数内可见,外面碰不到
return name
}模块化的本质目标就两条:① 每个文件有独立作用域,不互相污染;② 文件之间用显式 import/export 声明依赖,而不是偷偷读全局。
名词解释
全局作用域(Global Scope):不写在任何函数/模块里的变量,谁都能访问、谁都能改。它是「变量污染」的温床——早期多脚本项目里,两个文件同名变量会静默覆盖。模块化首先要消灭的就是「无意识共享全局」。
模块(Module):一个「有边界的代码单元」。它有自己的作用域(内部变量不外泄),并通过 export 主动暴露、用 import 主动引入别人。模块让「依赖关系」从「隐含(都读全局)」变成「显式(写在代码顶部)」,可读性、可维护性质的飞跃。
课后练习
练习 1:下面哪种写法能避免 name 被别的脚本覆盖?
// 写法A: var name = 'A'
// 写法B: (function(){ var name = 'A' })()答案:写法 B。IIFE(立即执行函数)创建独立作用域,
name不会泄漏到全局,别的脚本定义自己的name也不会冲突。这是模块化前最常用的人工隔离手段。
练习 2:为什么说「显式依赖」比「读全局」更好维护?
答案:显式
import让你一眼看清「这个文件依赖谁」;改一个模块,能立刻知道谁受影响。读全局则依赖关系散落在代码各处,重构时根本不知道改一处会波及哪里。
总结
我的观点:模块化不是「语法糖」,是「工程规模化的必然」。当你只有 200 行代码时,全局变量确实爽;但项目到 2 万行、10 个人协作时,全局污染和隐式依赖会让你每天救火。模块化的价值,本质是把「靠约定、靠记忆」变成「靠语法、靠工具」——作用域隔离靠语法强制,依赖关系靠 import 显式声明。理解了这点,你再看 ESM/CJS 的种种规则,就不会觉得是束缚,而是保护。