
在 javascript 中,直接通过 `derived.prototype.prop = value` 赋值无法覆盖父类原型上定义的 `object.defineproperty` getter/setter;必须使用 `object.defineproperty` 显式重定义,或在子类中声明同名访问器。
当你在父类 Something.prototype 上使用 Object.defineProperty 定义了一个带 get/set 的属性(如 abc),该属性具有非数据属性特性(即不是普通可写数据属性),那么它会沿着原型链生效:任何继承自 Something.prototype 的对象(包括 Derived.prototype 本身和 new Derived() 实例)在读写 abc 时,都会触发该 getter/setter —— 除非子类显式覆盖该属性定义。
❌ 错误做法:直接赋值覆盖(无效)
Derived.prototype.abc = "child value"; // ❌ 不会覆盖 getter/setter!
这行代码实际执行的是:在 Derived.prototype 对象上创建一个可写、可枚举、可配置的数据属性 abc,值为 "child value"。但由于 Derived.prototype 的 [[Prototype]] 指向 Something.prototype,而后者已定义了 abc 的 accessor(getter/setter),且该 accessor 是 non-enumerable(默认)且 configurable: true,此时关键点在于:
✅ Derived.prototype.abc = ... 触发的是 Something.prototype.abc 的 setter(因为 Derived.prototype 自身无 abc 数据属性,查找会落到原型上);
➡️ 所以 window.someValue 被修改,返回值仍是 "parent value" —— 这正是你观察到的“异常”行为,实则是 JS 原型访问规则的严格体现。
✅ 正确做法一:用 Object.defineProperty 显式重定义
Object.defineProperty(Derived.prototype, "abc", {
value: "child value",
writable: true,
enumerable: true,
configurable: true
});此方式在 Derived.prototype 上新建一个数据属性,屏蔽(shadow)了原型链上 Something.prototype.abc 的 accessor。后续 new Derived().abc 将直接读取该自有属性,不再触发父类 setter。
✅ 正确做法二:在子类中声明访问器(推荐,更语义化)
class Derived extends Something {
get b() {
return "inside child bValue";
}
// 显式覆盖 abc 访问器
get abc() {
return "child value";
}
set abc(value) {
console.log("Child setter called:", value);
// 可选择不调用父类逻辑,或 super.abc = value(若父类 setter 允许)
}
}此时 Derived.prototype.abc 是一个新的 accessor,优先级高于 Something.prototype.abc,完全隔离父类逻辑。
⚠️ 注意事项
- configurable: true 是前提:若父类 accessor 设为 configurable: false,则子类无法用 Object.defineProperty 覆盖(会抛错);
- 直接赋值 obj.abc = x 总是触发 setter(如果存在),而非创建新属性 —— 这是 ES5+ 的核心行为,与“是否在 prototype 上赋值”无关;
- 检查属性来源可用 Object.getOwnPropertyDescriptor(obj, 'abc') 或 Object.getPrototypeOf(obj).hasOwnProperty('abc')。
✅ 总结
要覆盖父类原型上的 accessor 属性,绝不可依赖简单赋值;必须通过 Object.defineProperty 在子原型上定义同名数据属性,或在子类中声明新的 getter/setter。这是 JavaScript 原型与属性描述符机制的设计本质,理解它能避免大量继承场景下的隐式副作用。










