JS继承中安全的属性访问控制关键在于明确访问边界:用#私有字段强制隔离、禁用下划线伪私有、闭包封装数据、模板方法暴露钩子而非状态。

JS 继承中实现安全的属性访问控制,关键不在“能不能继承”,而在于“谁能在哪访问、以什么方式访问”。真正的安全不是靠隐藏,而是靠明确的访问边界和受控的交互路径。
用 # 私有字段强制隔离内部状态
ES2022 起,# 语法提供语言级私有支持:字段仅在声明它的类内部可读写,子类哪怕在构造函数、方法或 getter 中尝试访问,都会直接报 TypeError 或编译失败。
- 父类定义
#id = 123,子类中this.#id会报错,super.#id同样不合法 - 这意味着子类无法绕过逻辑直接篡改底层数据,也杜绝了因误读命名约定导致的耦合
- 必须由父类主动暴露安全接口,比如
getId()或updateId(value),并在其中做校验、日志或副作用处理
避免下划线约定带来的“伪私有”陷阱
_name 这类命名只是开发者自觉,属性本身完全公开。子类能自由读写,看似方便,实则破坏封装。
- 子类可直接
this._name = 'new',跳过父类可能设定的格式校验或变更通知逻辑 - 一旦父类将来把
_name改为#name或重构为闭包私有,所有依赖该字段的子类都会崩溃 - 建议将这类字段视为“受保护但非私有”,只允许通过父类提供的方法操作,子类绝不直接触碰
用闭包+公有方法构建可控访问层
在构造函数内用 let 或 const 声明变量,配合绑定到 this 的方法,可实现真正不可外部穿透的私有数据。
- 例如:
function Parent() { let _balance = 0; this.getBalance = () => _balance; this.deposit = (n) => { if (n > 0) _balance += n; } } - 子类调用
super.getBalance()可读,但无法访问_balance本身;调用super.deposit()可改,但受父类逻辑约束 - 这种方式不依赖语法特性,兼容性好,且天然阻断子类对内部状态的任何直接干预
通过模板方法模式让子类参与流程而不接触数据
当子类需要定制行为(如校验、预处理、回调),不要让它操作私有字段,而是由父类定义骨架,留出钩子。
- 父类方法
save()内部调用this.beforeSave()和this.afterSave() - 这两个方法在父类中可为空实现或抛错,由子类选择性重写
- 整个流程由父类掌控,私有字段始终不暴露,子类只负责扩展逻辑,不负责管理状态











