@Bergi has already provided an explanation for why inheritance may not be the most suitable tool for dealing with types that require customization.
Instead, what is needed are techniques based on mixins for composing behaviors. In the specific example given, traits would be particularly beneficial as they allow for composition of behavior with options for overwriting, aliasing, omitting, and even modifying it while incorporating both traits and classes.
Given that JavaScript lacks support for traits, a solution resembling traits could involve utilizing patterns such as functions and proxy objects through closures, delegation via apply/call, and forwarding. For reference, a possible approach similar to this can be found in the response linked here.
In relation to the example code shared by the original poster, an implementation considering some of the mentioned techniques and mixin-patterns might look like the following:
function withFullyExposeX() {
this.valueOf = function () {
return this.x;
};
this.toString = function () {
return `{x:${this.x}}`;
};
}
var withExposeXValueAndShadowXStringify = (function (mixin) {
return function () {
mixin.call(this);
this.toString = function () {};
};
}(withFullyExposeX));
var withProxyOnlyExposesValueOfX = (function (mixin) {
var localProxy = {};
mixin.call(localProxy);
return function () {
this.valueOf = function () {
return localProxy.valueOf.call(this);
};
}
}(withFullyExposeX));
class X {
constructor(x) {
this.x = x;
}
}
class A extends X {}
withFullyExposeX.call(A.prototype);
class B extends X {}
withExposeXValueAndShadowXStringify.call(B.prototype);
class C extends X {}
withProxyOnlyExposesValueOfX.call(C.prototype);
var
x = new X('x'),
a = new A('a'),
b = new B('b'),
c = new C('c');
console.log('x.valueOf : ', x.valueOf);
console.log('a.valueOf : ', a.valueOf);
console.log('b.valueOf : ', b.valueOf);
console.log('c.valueOf : ', c.valueOf);
console.log('x.valueOf() : ', x.valueOf());
console.log('a.valueOf() : ', a.valueOf());
console.log('b.valueOf() : ', b.valueOf());
console.log('c.valueOf() : ', c.valueOf());
console.log('x.toString : ', x.toString);
console.log('a.toString : ', a.toString);
console.log('b.toString : ', b.toString);
console.log('c.toString : ', c.toString);
console.log('x.toString() : ', x.toString());
console.log('a.toString() : ', a.toString());
console.log('b.toString() : ', b.toString());
console.log('c.toString() : ', c.toString());
.as-console-wrapper { max-height: 100%!important; top: 0; }