@Bergi already did explain why inheritance is not the right tool dealing with this kind of shaping types.
What one is looking for are mixin based composition techniques. Due to the provided special example it even will be traits for they can handle composition of behavior with overwriting, aliasing, omitting and even modifying it while having involved both traits and classes.
Since JavaScript does not support traits a solution that comes close to it might look like the one provided at SO to "Mixins for ES6 classes, transpiled with babel"
The underlying patterns there are functions and proxy objects via closures, delegation via apply
/call
as well as forwarding.
A straightforward approach that refers to the OP's provided example code and also uses some of the above mentioned techniques and mixin-patterns could look like this one ...
function withFullyExposeX() { // function based mixin pattern ...
//
this.valueOf = function () { // ... implementing `this` bound
return this.x; // behavior that always has to be
}; // applied via `call` or `apply`
this.toString = function () { // onto any object type.
return `{x:${this.x}}`; //
}; //
} //
var withExposeXValueAndShadowXStringify = (function (mixin) {
return function () {
mixin.call(this); // - apply existing mixin/trait.
this.toString = function () {}; // - overwrite specific behavior.
};
}(withFullyExposeX));
var withProxyOnlyExposesValueOfX = (function (mixin) { // function based trait pattern ...
var localProxy = {}; //
mixin.call(localProxy); // ... that also is mixin based but
// uses a local proxy object in order
return function () { // to reuse the implementation of an
// already existing mixin or trait.
this.valueOf = function () { //
return localProxy.valueOf.call(this); // thus such a pattern not only uses
}; // explicit delegation via `apply`/`call`
//this.toString = function () {} // - overwrite. // but it in addition features forwarding
} // via it's encapsulated proxy object.
}(withFullyExposeX)); //
class X {
constructor(x) {
this.x = x;
}
}
class A extends X {}
// constructor(x) {
// //withFullyExposeX.call(this); // applying the mixin does work for both positions ...
// } //
//} //
withFullyExposeX.call(A.prototype); // ... but prototype in this case is the better choice.
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; }