-
-
Save krazov/9f2b050c86d572336c42e4062afe1d35 to your computer and use it in GitHub Desktop.
/* | |
(c) Jon Krazov 2019 | |
Below is an experiment searching boundaries of JavaScript. | |
It allows to compute one class out of many classes. | |
Usage 1: Without own constructor | |
If no constructor is passed then constructor of each class will be called | |
with params passed in object. In case of missing params, constructor | |
will be called without params. | |
Example: | |
const MyClass1 = computeClass([Class1, Class2, Class3]); | |
const myClass1Instance = new MyClass1({ | |
'Class1': [1, 2], | |
'Class2': ['test'], | |
'Class3': [(value) => value], | |
}); | |
Usage 2: With own constructor | |
If constructor is passed in options object (second param) then it will | |
be called in place of constructors of all classes. | |
Example: | |
const MyClass2 = computeClass([Class1, Class2, Class3], { | |
ownConstructor(param1) { | |
this.name = param1; | |
} | |
}); | |
const myClass2Instance = new MyClass2('Geoffrey'); | |
*/ | |
// actual function | |
var computeClass = (classes = [], { ownConstructor = null } = {}) => { | |
const noConstructor = (value) => value != 'constructor'; | |
const ComputedClass = ownConstructor === null | |
? class ComputedClass { | |
constructor(args) { | |
classes.forEach((Current) => { | |
const params = args[Current.name]; | |
if (params) { | |
Object.assign(this, new Current(...params)); | |
} else { | |
Object.assign(this, new Current()); | |
} | |
}) | |
} | |
} | |
: class ComputedClass { | |
constructor(...args) { | |
if (typeof ownConstructor != 'function') { | |
throw Error('ownConstructor has to be a function!'); | |
} | |
ownConstructor.call(this, ...args); | |
} | |
}; | |
const prototype = classes.reduce( | |
(composedPrototype, currentClass) => { | |
const partialPrototype = Object.getOwnPropertyNames(currentClass.prototype) | |
.reduce( | |
(result, propName) => | |
noConstructor(propName) | |
? Object.assign( | |
result, | |
{ [propName]: currentClass.prototype[propName] } | |
) | |
: result, | |
{} | |
); | |
return Object.assign(composedPrototype, partialPrototype); | |
}, | |
{} | |
); | |
Object.entries(prototype).forEach(([prop, value]) => { | |
Object.defineProperty(ComputedClass.prototype, prop, { value }); | |
}); | |
return ComputedClass; | |
} | |
// demo part | |
var A = class A { | |
constructor(a) { | |
this.a = a; | |
} | |
sayA() { console.log('I am saying A'); } | |
} | |
var B = class B { | |
constructor(b) { | |
this.b = b; | |
} | |
sayB() { console.log('I am saying B'); } | |
} | |
console.log('class A', A); | |
console.log('class B', B); | |
var C = computeClass([A, B]); | |
console.log('Composed class'); | |
console.log('var C = computeClass([A, B]);', C); | |
console.log('C.prototype', C.prototype); | |
var c = new C({ A: [2], B: [32] }); | |
console.log('var c = new C({ A: [2], B: [32] })', c); | |
console.log('c instanceof A', c instanceof A); | |
console.log('c instanceof B', c instanceof B); | |
console.log('Now c will say:') | |
c.sayA(); | |
c.sayB(); | |
console.log('---'); | |
var D = computeClass([A, B], { | |
ownConstructor(c) { | |
this.c = c; | |
} | |
}); | |
console.log(`var D = computeClass([A, B], { | |
ownConstructor(c) { | |
this.c = c; | |
} | |
});`); | |
var d = new D(42); | |
console.log('var d = new D(42)', d); | |
console.log('Now d will say:') | |
d.sayA(); | |
d.sayB(); | |
console.log('---'); | |
var E = computeClass(); | |
console.log('var E = computeClass();', E); | |
var e = new E(); | |
console.log('var e = new E()', e); |
Really nice :) I like it how you test/show samples at the bottom of the file! :)
var C = computeClass([A, B]); Why not use spread operator or even arguments to get rid of the array? :) var C = computeClass(A, B);
It would be great to see an example with clashing names eg. use a
in multiple classes and multiple values set in multiple classes - now it will probably overwrite it and last class will set its value. Is this intentional? :)
Awesome ;)
var C = computeClass([A, B]); Why not use spread operator or even arguments to get rid of the array? :)
var C = computeClass(A, B);
It was like that at first but then I wanted to pass options
with ownConstructor
. I could do them as computeClasses(A, B, { ownConstructor() {} });
and then do some slicing inside but being proof of concept I delegated this to user. Perhaps a closure like would also do,
const computeClass => (ownConstructor = null) => (...classes) => { /* magic */ };
It would be great to see an example with clashing names eg. use
a
in multiple classes and multiple values set in multiple classes - now it will probably overwrite it and last class will set its value. Is this intentional? :)
Yes, the last class wins but that was intended. There is no diamond resolution of methods. In this regard, it's more of classes mixin/composer, not actual multiple inheritance (but then again, JavaScript does not have classical inheritance).
Also, only direct prototype is being copied, so class Foo extends Bar {}
used as an argument will loose Bar.prototype
. All doable, of course, but that would be a behemoth of a function. That could show even further why this is not the best idea.
Dangerously cool... 😎