Last active
July 9, 2018 04:13
-
-
Save jmakeig/45cf2fa19103f8a870f11895f5350624 to your computer and use it in GitHub Desktop.
JavaScript inheritance with overrides and extensions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* Default implementation. Specialized classes should | |
* extend and override/extend on their own prototypes. | |
*/ | |
function Wrapper(wrapped) { | |
// Factory, not direct constructor | |
if(!this) { return new Wrapper(wrapped); } | |
// Internal (not private) property to track the wrapped object | |
Object.defineProperty(this, '_wrapped', { | |
enumerable: false, | |
configurable: false, | |
writable: true, | |
value: wrapped, | |
}); | |
return this; | |
} | |
Object.assign(Wrapper.prototype, { | |
doIt() { return 'FooWrapper.prototype.doIt'; }, | |
someOther() { return 'FooWrapper.prototype.someOther'; }, | |
}); | |
// Wrapped class | |
function Foo() {}; | |
Object.assign( | |
Foo.prototype, { | |
// Override method from inherited Wrapper.prototype | |
foo() { return 'Foo.prototype.foo'; }, | |
}); | |
// “Extends” the Wrapper class. (Yes, I should use ES6 classes) | |
function FooWrapper(wrapped) { | |
if(!(wrapped instanceof Foo)) { throw new TypeError('Can only wrap a Foo'); } | |
if(!this) { return new FooWrapper(wrapped); } | |
// Call the super class factory | |
return Wrapper.call(this, wrapped); | |
} | |
FooWrapper[Symbol.species] = () => Wrapper; | |
// Inherit the super class’s prototype | |
FooWrapper.prototype = Object.assign( | |
Object.create(Wrapper.prototype), { | |
// Override method from inherited Wrapper.prototype | |
doIt() { return 'FooWrapper.prototype.doIt' + ' >> ' + this._wrapped.foo(); }, | |
// Extend inherited Wrapper.prototype with new methods | |
myOwn() { return 'FooWrapper.prototype.myOwn' + ' >> ' + this._wrapped.foo(); } | |
}); | |
FooWrapper(new Foo()).doIt(); // FooWrapper.prototype.doIt | |
FooWrapper(new Foo()).someOther(); // Wrapper.prototype.someOther | |
FooWrapper(new Foo()).myOwn(); // FooWrapper.prototype.myOwn | |
//Wrapper(new Foo()).myOwn(); // Error! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
_The_ definitive way to set up prototypical inheritance in JavaScript (ES2015) without classes. | |
- Sets constructor property so type reporting is correct, such as in stack traces. | |
This has no bearing on `instanceof` or `Object.isPrototypeOf()`. | |
- Compact way to add getters and setters to an extended prototype | |
*/ | |
function Super() {} | |
Super.prototype.$1 = function() { /* … */ } | |
function Custom() {} | |
Custom.prototype = Object.create(Super.prototype, { | |
// Cleanest way to set property aspects, like get/set, enumerable, configurable | |
$2: { | |
get() { /* … */ } | |
} | |
}); | |
Object.assign(Custom.prototype, { | |
/* ALWAYS set the constructor function */ | |
constructor: Custom, | |
$3() {} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Argh. This really should be, simply: