Skip to content

Instantly share code, notes, and snippets.

@krazov
Last active January 2, 2021 13:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krazov/9f2b050c86d572336c42e4062afe1d35 to your computer and use it in GitHub Desktop.
Save krazov/9f2b050c86d572336c42e4062afe1d35 to your computer and use it in GitHub Desktop.
Computing class
/*
(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);
@SrAxi
Copy link

SrAxi commented Jun 5, 2019

Dangerously cool... 😎

@krzychukula
Copy link

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? :)

@animemixedworldgithub
Copy link

Awesome ;)

@krazov
Copy link
Author

krazov commented Jun 5, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment