Last active
January 2, 2021 13:39
-
-
Save krazov/9f2b050c86d572336c42e4062afe1d35 to your computer and use it in GitHub Desktop.
Computing class
This file contains 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
/* | |
(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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It was like that at first but then I wanted to pass
options
withownConstructor
. I could do them ascomputeClasses(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,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 looseBar.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.