易错点避坑指南:循环依赖 / 作用域 / Tree Shaking
本节目标
- 汇总模块化三大高频坑:循环依赖、作用域泄漏、Tree Shaking 失效
- 每条给出「症状 + 根因 + 解法」
- 形成「写模块前的自检清单」
坑 1:循环依赖拿到 undefined(CJS)
- 症状:
require对方导出是undefined。 - 根因:CJS 值拷贝 + 运行时顺序,回头 require 时对方没初始化完(见 jsm-l6)。
- 解法:重构单向依赖,或延迟到函数内
require。
坑 2:作用域泄漏(误用全局)
- 症状:两个模块变量互相覆盖、改一个影响另一个。
- 根因:没隔离作用域(忘了 IIFE/模块边界,或迁移时残留全局)。
- 解法:每个文件是模块,变量用
export显式暴露,绝不挂全局。
坑 3:Tree Shaking 抖不掉
- 症状:打包体积没变小,明明没用的导出还在。
- 根因:用了 CJS(无法静态)、或模块顶层有副作用、或
export *桶文件。 - 解法:改 ESM 命名导出、模块纯净、按需直接引具体文件(见 jsm-l12)。
自检清单(写模块前过一遍):
□ 这个文件只做一件事吗?(单一职责)
□ 依赖是单向的吗?有环就抽中间层
□ 导出用命名导出吗?顶层有副作用吗?
□ 跨模块读取放进函数、不在顶层急用吗?名词解释
避坑清单(Checklist):把「常见错误」变成「写代码前的自检项」。模块化三坑(循环依赖、作用域泄漏、Tree Shaking 失效)各有根因,但都能在「动手前」靠清单规避。清单的价值是「把事后调试变成事前预防」,成本最低。
作用域泄漏(Scope Leak):本该在模块内的变量跑到了全局,导致跨模块互相覆盖。它是「没真正模块化」的典型症状——根因常是迁移残留全局、或忘了文件边界。诊法是「搜全局 var/let 是否挂在 window、是否被多文件共享」。
课后练习
练习 1:三个坑里哪个「编译期就能发现」,哪个「只能运行时偶发」?
答案:作用域泄漏常「编译不报错、运行才覆盖」,最难查;循环依赖(CJS)也是运行时偶发 undefined;Tree Shaking 失效不报错只是体积大。三者都「不报语法错」,所以靠清单预防比靠报错更重要。
练习 2:自检清单里「跨模块读取放进函数」主要防哪个坑?
答案:主要防「循环依赖半成品」(CJS 下顶层急用对方未初始化导出会 undefined)。延迟到函数内读取,等所有模块初始化完再调用,对方早已就绪。ESM 下配合 live binding 也更稳(见 jsm-l9)。
总结
这一节是整套课程的「安全带」。我的观点:模块化的坑几乎都不报语法错,全在运行时或体积上阴你,所以预防远胜调试。三个坑各有根因但同源——都是「依赖关系没想清」:循环依赖是方向问题、作用域泄漏是边界问题、Tree Shaking 失效是纯度问题。把那张四题自检清单贴在编辑器边,写每个模块前扫一眼,能挡掉 90% 的模块化事故。最后一句送给所有学到这的人:模块化不是语法,是「对依赖关系的诚实」——你越诚实地理清「谁依赖谁、暴露什么」,代码就越不乱、越不怕改。