const mixinProps = (target, source) => {
Object.getOwnPropertyNames(source).forEach(prop => {
if (/^(?:constructor|isInstanceOf)$/.test(prop)) { return; }
Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
})
};
const mroMerge = (list) => {
if (!list || !list.length) {
return [];
}
for (let items of list) {
let item = items[0];
let valid = true;
for (let items2 of list) {
if (items2.indexOf(item) > 0) {
valid = false;
break;
}
}
if (valid) {
nextList = [];
for (let items3 of list) {
let _index = items3.indexOf(item);
if (_index > -1) {
items3.splice(_index, 1);
}
items3.length && nextList.push(items3);
}
return [item, ...mroMerge(nextList)];
}
}
throw new Error('Unable to merge MRO');
};
const c3mro = (ctor, bases) => {
if (!bases || !bases.length) {
return [ctor];
}
let list = bases.map(b => b._meta.bases.slice());
list = list.concat([bases]);
let res = mroMerge(list);
return [ctor, ...res];
};
const createClass = (parents, props) => {
const isMulti = parents && Array.isArray(parents);
const superCls = isMulti ? parents[0] : parents;
const mixins = isMulti ? parents.slice(1) : [];
const Ctor = function(...args) {
// TODO: call each parent's constructor
if (props.constructor) {
props.constructor.apply(this, args);
}
};
// save c3mro into _meta
let bases = [superCls, ...mixins].filter(item => !!item);
Ctor._meta = { bases: c3mro(Ctor, bases) };
if (superCls && typeof superCls === 'function') {
Ctor.prototype = Object.create(superCls.prototype);
Ctor.prototype.constructor = Ctor;
}
// mix into prototype according to [Method Resolution Order]
if (Ctor._meta.bases.length > 1) {
let providers = Ctor._meta.bases.slice(1).reverse();
providers.forEach(provider => {
// TODO: prototype of superCls is already inherited by __proto__ chain
(provider !== superCls) && mixinProps(Ctor.prototype, provider.prototype);
});
}
mixinProps(Ctor.prototype, props);
Ctor.prototype.isInstanceOf = function(cls) {
let bases = this.constructor._meta.bases;
return bases.some(item => item === cls) || (this instanceof cls);
}
return Ctor;
};
// ==================
// for test only
// ==================
const O = createClass(null, {});
const X = createClass([O], {});
const Y = createClass([O], {
methodY() { return 'Y'; }
});
const A = createClass([X, Y], {
testName() { return 'A'; }
});
const B = createClass([Y], {
testName() { return 'B'; }
});
const C = createClass([A, B], {
constructor() {
this._name = 'custom C';
}
});
let obj = new C();
console.log(obj.isInstanceOf(O)); // true
console.log(obj.isInstanceOf(X)); // true
console.log(obj.isInstanceOf(Y)); // true
console.log(obj.isInstanceOf(A)); // true
console.log(obj.isInstanceOf(B)); // true
console.log(obj.isInstanceOf(C)); // true
console.log(obj.testName());
console.log(obj.methodY());