如何实现JavaScript类指定方法不可重写,其余方法可重写?
如何在JavaScript中让类的指定方法不可重写,同时保留其他方法的可重写能力?
问题描述
我们需要实现这样的类继承逻辑:
- 基类
Base包含两个方法:talk和walk - 子类
Derived继承Base后,不能重写talk方法(子类定义同名方法应报错或不生效,调用时仍执行基类逻辑) - 子类
Derived可以正常重写walk方法,调用时执行子类逻辑
期望的伪代码示例:
class Base { final talk() { console.log("I am a talking class!"); } walk() { console.log("I am walking!"); } } class Derived extends Base { talk() // 此处应报错 { console.log("I don't talk!"); } walk() // 此处正常允许 { console.log("I am not walking!"); } } let der = new Derived(); der.talk(); // 应输出 "I am a talking class!" der.walk(); // 应输出 "I am not walking!"
背景参考
在C++中可以通过final关键字直接实现这个需求,示例代码如下:
#include <iostream> class Base { public: virtual void Start () final { std::cout << "Starting!"; } virtual void End() { std::cout << "Ending!"; } }; class Derived : Base { public: void Start () { std::cout << "Not starting!"; } void End() { std::cout << "Not ending!"; } }; int main () { Derived *newDerived= new Derived(); newDerived->End(); return 0; }
编译时会直接报错,提示子类重写了被标记为final的方法:
main.cpp:13:8: error: virtual function ‘virtual void Derived::Start()’ overriding final function 13 | void Start () | ^~~~~ main.cpp:5:16: note: overridden function is ‘virtual void Base::Start()’ 5 | virtual void Start () final |
但在TypeScript中没有找到对应实现,JavaScript原生也不支持类方法的final修饰符。此前尝试用Object.defineProperty()的方案,只能让方法在实例上不可变,但子类仍然可以通过定义同名方法遮蔽基类方法,无法达到预期效果。
解决方案
以下是可行的JavaScript实现代码,通过实例属性锁定+私有辅助方法的方式,实现指定方法不可重写,同时保留其他方法的可重写能力:
"use strict"; class Base { #name; #makeMethodsFinal(methods) { for (const [methodName, method] of Object.entries(methods)) { Object.defineProperty(this, methodName, { writable: false, configurable: true, value: method.bind(this) }); } } leave() { console.log("Bye!"); } constructor(name) { this.#name = name; const finalMethods = { present: () => { console.log(`Hello! My name is ${this.#name}`); } }; this.#makeMethodsFinal(finalMethods); } } class Derived extends Base { // 尝试重写final方法,严格模式下会报错 present() { console.log("This won't be printed"); } // 正常重写非final方法 leave() { console.log("It was nice seeing you!"); } } let der = new Derived("Franco"); der.present(); // 输出 "Hello! My name is Franco" der.leave(); // 输出 "It was nice seeing you!"
实现原理
- 在基类的构造函数中,将需要标记为“不可重写”的方法定义为实例属性,而非原型方法
- 通过
Object.defineProperty()将这些实例属性的writable设置为false,严格模式下子类尝试赋值该属性会直接报错,非严格模式下赋值会静默失败 - 利用箭头函数或
bind绑定this,确保方法能正常访问基类的私有属性 - 其他未被标记的方法(如
leave)仍然是原型方法,子类可以正常重写
内容的提问来源于stack exchange,提问作者vitopigno
相关产品推荐
相关产品推荐

