ESM模块中为何能访问未导出且未在当前文件声明的对象?
嘿,兄弟,你遇到的这个情况听起来有点反常识对吧?按ESM模块的规则,没导出的变量/实例那可是模块内部的“私有财产”,别的模块根本没权限碰才对。那为啥你这儿居然能运行?我帮你排查下最可能的几个原因:
你其实在当前文件重新创建了实例,只是自己没反应过来
比如你第一个文件里写了const testInstance = new TestClass(),然后把这段代码复制到第二个文件——别忘了你已经在第二个文件里导入了TestClass啊!这段复制的代码其实会在第二个模块里重新生成一个全新的实例,不是第一个文件里那个未导出的私有实例。你觉得“能访问”,其实是用了自己刚创建的新实例,不是第一个文件里的那个。不信你可以给实例加个唯一标记(比如构造函数里给每个实例加个随机id),打印出来看看,两个实例的id肯定不一样。不小心把实例挂到全局对象上了
要是你在第一个文件里写代码的时候,顺手把实例挂载到全局了(比如浏览器环境里写了window.testInstance = new TestClass(),Node.js里写了global.testInstance = ...或者globalThis.testInstance = ...),那全局变量不管在哪个模块都能访问到,这时候哪怕没导出,也能拿到。不过这种情况其实是不小心污染了全局环境,可不是ESM模块本身的特性哦。别信模块缓存的锅,这不可能
有人可能会猜是不是模块缓存导致私有实例“漏”出来了,但ESM的缓存只保存模块的导出内容,未导出的私有变量还是被模块牢牢封装着,缓存根本不会让外部模块拿到。所以这个原因可以直接排除。
给你举个简单的例子验证下:
第一个文件tests.js:
export class MyClass { sayHello() { console.log('Hello!') } } // 未导出的私有实例 const privateInstance = new MyClass(); privateInstance.sayHello(); // 这里只有当前模块能调用
第二个文件index.js:
import { MyClass } from './tests.js'; // 如果你复制的是const privateInstance = new MyClass(); privateInstance.sayHello(); // 这时候是你自己新创建的实例,和tests.js里的不是同一个 // 但如果你直接写privateInstance.sayHello(); 那绝对会报ReferenceError,因为当前模块根本没这个变量
你回头仔细看看自己的代码,大概率是第一种情况——复制过来的代码其实是重新创建了实例,而不是直接引用第一个文件里的未导出实例~
内容来源于stack exchange

