易错点避坑指南:循环依赖 / 作用域 / Tree Shaking

本节目标

坑 1:循环依赖拿到 undefined(CJS)

坑 2:作用域泄漏(误用全局)

坑 3:Tree Shaking 抖不掉

自检清单(写模块前过一遍):

□ 这个文件只做一件事吗?(单一职责)
□ 依赖是单向的吗?有环就抽中间层
□ 导出用命名导出吗?顶层有副作用吗?
□ 跨模块读取放进函数、不在顶层急用吗?

名词解释

避坑清单(Checklist):把「常见错误」变成「写代码前的自检项」。模块化三坑(循环依赖、作用域泄漏、Tree Shaking 失效)各有根因,但都能在「动手前」靠清单规避。清单的价值是「把事后调试变成事前预防」,成本最低。

作用域泄漏(Scope Leak):本该在模块内的变量跑到了全局,导致跨模块互相覆盖。它是「没真正模块化」的典型症状——根因常是迁移残留全局、或忘了文件边界。诊法是「搜全局 var/let 是否挂在 window、是否被多文件共享」。

课后练习

练习 1:三个坑里哪个「编译期就能发现」,哪个「只能运行时偶发」?

答案:作用域泄漏常「编译不报错、运行才覆盖」,最难查;循环依赖(CJS)也是运行时偶发 undefined;Tree Shaking 失效不报错只是体积大。三者都「不报语法错」,所以靠清单预防比靠报错更重要。

练习 2:自检清单里「跨模块读取放进函数」主要防哪个坑?

答案:主要防「循环依赖半成品」(CJS 下顶层急用对方未初始化导出会 undefined)。延迟到函数内读取,等所有模块初始化完再调用,对方早已就绪。ESM 下配合 live binding 也更稳(见 jsm-l9)。

总结

这一节是整套课程的「安全带」。我的观点:模块化的坑几乎都不报语法错,全在运行时或体积上阴你,所以预防远胜调试。三个坑各有根因但同源——都是「依赖关系没想清」:循环依赖是方向问题、作用域泄漏是边界问题、Tree Shaking 失效是纯度问题。把那张四题自检清单贴在编辑器边,写每个模块前扫一眼,能挡掉 90% 的模块化事故。最后一句送给所有学到这的人:模块化不是语法,是「对依赖关系的诚实」——你越诚实地理清「谁依赖谁、暴露什么」,代码就越不乱、越不怕改。