JavaScript构造函数虽不支持原生私有字段,但可通过#私有字段(ES2022+推荐)、闭包+WeakMap、避免调用可重写方法、入参白名单校验四种方式,在实例初始化阶段隔离核心数据,确保外部与子类无法直接访问。

JavaScript 构造函数本身不支持原生私有字段,但可以通过几种成熟方式在实例初始化阶段就隔离核心数据,让外部和子类无法直接读写——关键不是“藏起来”,而是“够不着”。
用 # 私有字段(ES2022+,最推荐)
现代 JS 允许在 class 内部直接声明以 # 开头的字段,它们在语法层面不可被继承、不可被枚举、不可被 this 访问(除非在定义它的类内部)。
- 私有字段只能在定义它的 class 主体中访问,子类哪怕重写同名方法,也根本无法引用 this.#token
- 构造函数里可直接初始化:#token = crypto.randomUUID();#config = { timeout: 5000 };
- 配合私有方法做校验:#validate() { return this.#token && this.#token.length > 10; }
用闭包 + 构造函数(兼容旧环境)
在构造函数作用域内声明变量,再通过 getter/setter 或方法暴露受控接口,变量本身不会挂到 this 上,也不进原型链。
- 每个实例独享一份私有状态,不会共享或污染
- 示例:const _id = Symbol('id'); const _data = new WeakMap(); _data.set(this, { raw: options.data });
- 对外只提供 get id() { return _data.get(this)?.raw?.id; },不暴露原始存储结构
避免在构造中调用可被重写的方法
这是常被忽略的隐患:如果 constructor 里直接调用 this.init(),而 init 被子类重写,就可能在 this 尚未完全初始化时执行逻辑,导致错误或数据泄漏。
- 正确做法:把核心初始化逻辑放进私有方法(如 #initCore()),构造函数只调用它
- 公有 init() 方法应只是安全包装:init() { this.#initCore(); return this; }
- 这样即使子类重写 init,也不会干扰父类关键路径
入参白名单 + 初始化守卫
构造函数是第一道防线。别假设传进来的参数都可信,尤其当类可能被继承或用于配置对象时。
- 对 options 参数做键名校验,只接受已知字段,多余字段静默丢弃或报错
- 必要时加运行时断言:if (typeof config.timeout !== 'number' || config.timeout
- 初始化完成后,可冻结关键对象:Object.freeze(this.#internalState);


















