foibles
Version:
Composition and mixins and TypeScript classes
73 lines • 2.24 kB
JavaScript
// Symbol used to mark if a certain mixin has been applied
const application = Symbol('decorator:application');
/**
* Check if the given prototype has the given mixin applied to itself.
*
* @param object
* @param mixin
*/
function has(object, mixin) {
while (object != null) {
if (object.hasOwnProperty(application) && object[application] === mixin) {
return true;
}
object = Object.getPrototypeOf(object);
}
}
/**
* Turn a function into a fully featured mixin. The mixin function should be
* a function which takes a single argument which is the class to extend and
* the function must return an extended class.
*
* ```javascript
* const ExampleMixin = toMixin(base => class extends base {
* ...
* });
* ```
*
* @param func
*/
export function toMixin(func) {
/*
* Define a function that checks if the mixin has already been applied
* to the prototype chain. This allows mixins to use other mixins as needed
* without them being applied twice.
*/
const mixinOnce = function (base) {
if (has(base.prototype, func))
return base;
const result = func(base);
result.prototype[application] = func;
return result;
};
/*
* Define a custom hasInstance symbol so that instanceof can be used if
* the environment supports it.
*/
if (Symbol.hasInstance) {
if (func.hasOwnProperty(Symbol.hasInstance)) {
/*
* Mixin has its own hasInstance, let the outer function delegate
* to it.
*/
Object.defineProperty(mixinOnce, Symbol.hasInstance, {
value: function mixinHasInstance(other) {
return func[Symbol.hasInstance](other);
}
});
}
else {
/*
* The mixin function does not have a custom hasInstance, use our
* own.
*/
Object.defineProperty(mixinOnce, Symbol.hasInstance, {
value: function mixinHasInstance(other) {
return has(other, func);
}
});
}
}
return mixinOnce;
}
//# sourceMappingURL=mixin.js.map