“完美继承”指子类访问器不遮蔽父类、保持原型委托、统一微任务执行:需确保父类访问器configurable为true,子类避免同名重写或直接赋值导致数据属性覆盖,重写时应复用父类异步逻辑。

要确保子类“完美继承”父类中带有微任务(如 Promise.then、queueMicrotask)的访问器属性(getter/setter),关键不在“继承”本身,而在于**避免遮蔽、保持委托、统一控制执行时机**。JavaScript 中访问器属性本就不复制,而是动态沿原型链查找;所谓“完美”,是指子类读写该属性时,仍能准确触发父类定义的异步逻辑,不意外降级为数据属性,也不破坏微任务队列顺序。
明确访问器属性不自动“继承行为”,只委托执行
父类用 Object.defineProperty 或 class 语法在原型上定义了含微任务的 getter/setter(例如:getter 内部 return Promise.resolve(value).then(...)),子类实例只要自身没定义同名访问器,访问时就会自动调用父类原型上的函数——这是默认行为,无需额外操作。
- ✅ 正确:子类不声明同名 get/set → 每次访问都走父类逻辑,微任务照常入队
- ❌ 错误:子类写了
get prop() { return this._value; }→ 遮蔽父类,微任务逻辑彻底丢失 - ⚠️ 危险:子类直接赋值
obj.prop = x,而父类 setter 缺失或未处理 → 实例上生成数据属性,后续读取返回undefined或原始值,不再触发任何微任务
子类重写时必须显式复用父逻辑,不可绕过
若子类需扩展或调整行为(比如在微任务前加校验、或追加另一个 then),不能另起炉灶,而应通过 super 调用父访问器,并在其返回的 Promise 上链式处理:
- 在 class 中,用
get prop() { return super.prop.then(...); }(注意:不能写super.prop直接读值,必须调用super.getProp()形式,但 class 语法不支持该写法;正确做法是父类暴露可复用的受保护方法,如_getPropRaw()) - 更稳妥:父类把核心异步逻辑抽成普通方法(如
_fetchValue()返回 Promise),getter/setter 只负责调用它;子类重写访问器时,调用super._fetchValue()并加工 - 避免在 setter 内直接
await——这会阻塞同步流程;保持返回 Promise,让调用方决定是否await
描述符配置必须保证可重定义,尤其 configurable
若父类用 Object.defineProperty 在原型上添加访问器,默认 configurable: false,会导致子类无法重写同名访问器(调用 defineProperty 会抛错)。这对“完美继承”是硬性阻碍:
- ✅ 父类定义时显式设
configurable: true:Object.defineProperty(Person.prototype, 'name', { get() { ... }, set(v) { ... }, configurable: true }) - ✅ 优先使用 ES6 class 语法:
class Person { get name() { return Promise.resolve(this._name).then(...); } }——此类访问器默认configurable: true,安全可重写 - ❌ 不要在运行时反复修改原型;统一在类定义阶段完成,避免竞态和不可预测覆盖
警惕数据属性与访问器混用导致的静默降级
这是最易忽略的破环点:父类用访问器控制属性,子类却用简单赋值,结果不是报错,而是悄悄创建同名数据属性,从此永远绕过微任务逻辑:
- 父类 setter 若未实现,或仅做空操作,子类
inst.prop = x就会直接在实例上加prop: x - 补救方案:父类 setter 中主动校验,发现非预期写入时抛错或 warn:
set prop(val) { if (!isAsyncReady()) throw new Error('prop must be set via async flow'); ... } - 或用
Object.defineProperty(this, 'prop', { value: ..., writable: false, configurable: false })在实例初始化时锁定,防止后续覆盖










