@webqit/util
Version:
Utility functions used accross multiple JS libraries.
114 lines (111 loc) • 3.32 kB
JavaScript
/**
* @imports
*/
import _isArray from './isArray.js';
import _isFunction from './isFunction.js';
import _arrLast from '../arr/last.js';
import _mergeCallback from '../obj/mergeCallback.js';
import _each from '../obj/each.js';
/**
* A multi-inheritance implementstion.
*
* @param array ...classes
*
* @return object
*/
const Implementations = new Map;
export default function mixin(...classes) {
var Traps = {};
var RetrnDirective = 'last';
if (_isArray(arguments[0])) {
classes = arguments[0];
Traps = arguments[1];
if (arguments[2]) {
RetrnDirective = arguments[2];
}
}
// -----------------------
var Base = _arrLast(classes);
var supersMap = {};
// -----------------------
// Create the Mixin
// ...with a special constructor.
// -----------------------
var Mixin = class {
constructor(...args) {
classes.forEach((_class, i) => {
Reflect.construct(_class, args, this.constructor);
});
}
};
// -----------------------
// Implement a special handler of the "instanceof" operator.
// -----------------------
classes.forEach((_class, i) => {
if (!Implementations.has(_class)) {
Implementations.set(_class, []);
try {
var originalInstanceChecker = _class[Symbol.hasInstance];
Object.defineProperty(_class, Symbol.hasInstance, {value: function(instance) {
if (originalInstanceChecker.call(this, instance)) {
return true;
}
if (Implementations.has(this)) {
return Implementations.get(this).reduce((yes, _mixin) => yes || (instance instanceof _mixin), false);
}
return false;
}});
} catch (e) {
throw new Error('Cannot mixin the class at index ' + i + '. Class may already have been configured for instance checks somewhere.');
}
}
Implementations.get(_class).push(Mixin);
});
// ---------------------
// Mixin both static and instance properties and methods
// ---------------------
classes.forEach(_class => {
// Copy const members
_mergeCallback([Mixin, _class], (key, obj1, obj2) => ![
'name', 'prototype', 'prototypes', 'length', 'caller', 'callee', 'arguments', 'constructor', 'apply', "bind", 'call', 'toString',/**/
].includes(key), true/*deepProps*/);
_mergeCallback([Mixin.prototype, _class.prototype], (key, obj1, obj2) => {
if (!['prototype', 'prototypes'].includes(key)) {
if (_isFunction(obj2[key])) {
if (_isArray(supersMap[key])) {
supersMap[key].push(obj2[key]);
} else {
supersMap[key] = [obj2[key]];
}
return false;
}
return true;
}
return false;
}, true/*deepProps*/);
});
// Extend (proxy) methods
_each(supersMap, (name, supers) => {
if (name === 'constructor') {
return;
}
// NOTE: this must not be defined as an arrow function
// for the benefit of the "this".
Mixin.prototype[name] = function(...args) {
if (Object.hasOwnProperty(Traps, name) && _isFunction(Traps[name])) {
// Wrap a call to the trap...
// So mixin supers are passed to traps
return Traps[name].call(this, supers, ...args);
} else {
// Call each super and return
// the last one's return value
var supersReturnValues = [];
supers.forEach(supr => {
supersReturnValues.push(supr.call(this, ...args));
})
return _arrLast(supersReturnValues);
}
};
});
return Mixin;
};