JavaScript模块无真正私有机制,依赖词法作用域和闭包模拟私有;ES6模块通过顶层隐式函数作用域隔离变量,未导出即不可访问;实现真正私有需用闭包、#私有字段或WeakMap。

JavaScript模块系统本身不提供真正的私有变量保护机制,它依赖的是词法作用域和闭包特性来实现“模拟私有”。ES6模块(import/export)仅控制符号的导出与访问边界,并不阻止内部变量被外部读取——真正起作用的是模块顶层作用域的封装性,而非语法级的访问控制。
模块顶层作用域即天然闭包
每个ES6模块文件在运行时都会被自动包裹在一个隐式的函数作用域中(类似IIFE),模块内声明的变量、函数、类默认不会挂载到全局,也无法被其他模块直接访问:
-
模块A.js:
const secret = 'I am hidden';<br>export const publicValue = 42;
-
模块B.js导入后:
import { publicValue } from './A.js';<br>console.log(publicValue); // ✅ 42<br>console.log(secret); // ❌ ReferenceError: secret is not defined
这里的 secret 并非被“权限拦截”,而是根本未被导出,且因模块作用域隔离,外部无法通过任何方式引用它——这是静态作用域限制,不是运行时保护。
真正私有需靠闭包或#私有字段
若要在模块内暴露一个对象/类,同时隐藏其内部状态,则必须主动构造封闭环境:
立即学习“Java免费学习笔记(深入)”;
-
闭包模式(传统方式):
通过工厂函数返回仅暴露方法的对象,内部变量保留在外层函数作用域中,无法从外部触及。 -
类私有字段(ES2022+):
使用#name语法声明的字段只能在类定义内部访问,即使该类被导出,私有字段也无法被实例外部读写(违反会抛TypeError)。 -
WeakMap 模拟私有(兼容旧环境):
用WeakMap以实例为键存储私有数据,避免属性名暴露,但需手动管理映射关系。
注意:Tree-shaking 与私有无关
有人误以为未导出的变量会被打包工具(如Webpack、Rollup)自动移除,等同于“更安全”。其实 tree-shaking 只影响未被引用的导出项是否进入最终 bundle,它不改变运行时可访问性逻辑。一个未导出但被模块内函数闭包捕获的变量,依然存在于内存中,只是不可从外部命名访问。
CommonJS 的差异仅在于包装形式
Node.js 中的 CommonJS 模块(require/module.exports)同样依赖作用域隔离:每个文件被包装成 (function(exports, require, module, __filename, __dirname) { ... })。因此 const foo = 1 不赋值给 exports 就对外不可见——原理一致,只是实现细节不同。










