Sorry you all, I realized I should've simplified it. Here's a [simpler fiddle](https://jsfiddle.net/trusktr/tf6hdn48/6/).
On Thu, Nov 21, 2019 at 1:17 PM #!/JoePea <j...@trusktr.io> wrote: > > I was trying to implement "multiple inheritance" in the following code > ([jsfiddle](https://jsfiddle.net/trusktr/tf6hdn48/)), but it gives a > max call stack (infinite recursion) error. > > However, the infinite recursion does not execute any of my console.log > statements repeatedly like I'd expect, so it seems that the infinite > recursion is happening inside the JS engine? > > What's going on? > > Here's the code for reference, see "PROXY INFINITE RECURSION" comments > for the site where the problem is happening (as far as I can tell in > devtools): > > ```js > "use strict"; > > function multiple(...classes) { > if (classes.length === 0) > return Object; > if (classes.length === 1) { > const result = classes[0]; > return (typeof result === 'function' ? result : Object); > } > > const FirstClass = classes.shift(); > const __instances__ = new WeakMap(); > const getInstances = (inst) => { > let result = __instances__.get(inst); > if (!result) > __instances__.set(inst, (result = [])); > return result; > }; > > class MultiClass extends FirstClass { > constructor(...args) { > super(...args); > > const protoBeforeMultiClassProto = > findPrototypeBeforeMultiClassPrototype(this, MultiClass.prototype); > if (protoBeforeMultiClassProto) > Object.setPrototypeOf(protoBeforeMultiClassProto, > newMultiClassPrototype); > > const instances = getInstances(this); > > for (const Ctor of classes) > instances.push(new Ctor(...args)); > } > } > > let count = 0; > const newMultiClassPrototype = new Proxy({ > __proto__: MultiClass.prototype, > }, { > get(target, key, self) { > if (count++ < 500) console.log(' --------------- get', key); > > if (Reflect.has(MultiClass.prototype, key)) > return Reflect.get(MultiClass.prototype, key, self); > > for (const instance of getInstances(self)) > if (Reflect.has(instance, key)) > return Reflect.get(instance, key, self); > > return undefined; > }, > > set(target, key, value, self) { > console.log(' ----- set1', key, value); > > // PROXY INFINITE RECURSION HERE: > console.log('hmmmmmmmmmmmmmmmm?', > Reflect.has(MultiClass.prototype, key)); > > console.log(' ----- set1.5', key, value); > > if (Reflect.has(MultiClass.prototype, key)) { > return Reflect.set(target, key, value, self); > } > > const instances = getInstances(self); > > for (const instance of instances) { > if (Reflect.has(instance, key)) { > return Reflect.set(instance, key, value, self); > } > } > > return Reflect.set(target, key, value, self); > }, > }); > > return MultiClass; > } > > function findPrototypeBeforeMultiClassPrototype(obj, multiClassPrototype) { > let previous = obj; > let current = Object.getPrototypeOf(obj); > while (current) { > // debugger > if (current === multiClassPrototype) > return previous; > previous = current; > current = Object.getPrototypeOf(current); > } > return null; > } > > async function test() { > await new Promise(r => setTimeout(r, 3000)); > console.log('-------------------------------------'); > const R1 = multiple(); > const r1 = new R1(); > console.log(Object.keys(r1)); > console.log('-------------------------------------'); > class Foo { > constructor() { > this.f = false; > } > } > const R2 = multiple(Foo); > const r2 = new R2(); > console.log(r2.hasOwnProperty); > console.log('f', r2.f); > console.log('-------------------------------------'); > class Bar { > constructor() { > this.b = 'asf'; > } > } > const R3 = multiple(Foo, Bar); > const r3 = new R3(); > console.log(r3.hasOwnProperty); > console.log('f', r3.f); > console.log('b', r3.b); > console.log('-------------------------------------'); > class Baz { > constructor() { > this.z = 1; > } > } > const R4 = multiple(Foo, Bar, Baz); > const r4 = new R4(); > console.log(r4.hasOwnProperty); > console.log('f', r4.f); > console.log('b', r4.b); > console.log('z', r4.z); > class One { > constructor(arg) { > this.one = 1; > console.log('One constructor'); > // this.one = arg > } > foo() { > console.log('foo', this.one); > } > setVar() { > this.var = 'bright'; > } > } > class Two { > constructor(arg) { > this.two = 2; > console.log('Two constructor'); > // this.two = arg > } > bar() { > console.log('bar', this.two); > } > readVar() { > console.log(this.var); // should be "bright" > } > } > class Three extends Two { > constructor(arg1, arg2) { > super(arg1); > this.three = 3; > console.log('Three constructor'); > // this.three = arg2 > } > baz() { > console.log(' - baz: call super.bar'); > super.bar(); > console.log('baz', this.three, this.two); > } > } > class FooBar extends multiple(Three, One) { > constructor(...args) { > super(); > // call each constructor. We can pas specific args to each > constructor if we like. > // > // XXX The following is not allowed with ES6 classes, > class constructors are not callable. :[ How to solve? > // One.call(this, ...args) > // Three.call(this, ...args) > // > // XXX Solved with the callSuperConstructor helper. > // ;(this as any).callSuperConstructor(One, args[0]) > // ;(this as any).callSuperConstructor(Three, args[1], args[2]) > } > yeah() { > console.log(' -- yeah', this.one, this.two, this.three); > super.baz(); > super.foo(); > } > } > let f = new FooBar(1, 2, 3); > // this shows that the modifications to `this` by each constructor worked: > console.log(f.one, f.two, f.three); // logs "1 2 3" > console.log(' ---- call methods:'); > // all methods work: > f.foo(); > f.bar(); > f.baz(); > f.yeah(); > f.setVar(); > f.readVar(); > console.log(' --------------------------- '); > class Lorem { > constructor() { > this.lo = 'rem'; > } > } > // class Ipsum extends multiple(Lorem, FooBar) { > class Ipsum extends multiple(FooBar, Lorem) { > constructor() { > super(...arguments); > // THIS LINE TRIGGER PROXY INFINITE RECURSION > this.ip = 'sum'; > } > test() { > console.log(' -- Ipsum: call super.bar()'); > console.log(super.foo); > console.log(super.bar); > console.log(super.baz); > console.log(super.yeah); > console.log(super.setVar); > console.log(super.readVar); > super.bar(); > console.log(this.lo, this.ip); > } > } > const i = new Ipsum(); > i.foo(); > i.bar(); > i.baz(); > i.yeah(); > i.setVar(); > i.readVar(); > i.test(); > function createOnChangeProxy(onChange, target) { > return new Proxy(target, { > get(target, property) { > const item = target[property]; > if (isMutableObject(item)) > return createOnChangeProxy(onChange, item); > return item; > }, > set(target, property, newValue) { > ; > target[property] = newValue; > onChange(); > return true; > }, > }); > } > function isMutableObject(maybe) { > if (maybe === null) > return false; > if (maybe instanceof Date) > return false; > // treat Uint8Arrays as immutable, even though they > technically aren't, because we use them a lot and we treat them as > immutable > if (maybe instanceof Uint8Array) > return false; > // TODO: filter out any other special cases we can find, where > something identifies as an `object` but is effectively immutable > return typeof maybe === 'object'; > } > let changeCount = 0; > const o = createOnChangeProxy(() => changeCount++, {}); > o.foo = 1; > o.bar = 2; > o.baz = {}; > o.baz.lorem = true; > o.baz.yeee = {}; > o.baz.yeee.wooo = 12; > console.log(changeCount === 6); > } > test(); > ``` _______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss