3

I have an existing prototype hierarchy and I want to modify it so that the hierarchy is kept intact but an additional prototype is added to then end of it. instanceof should return true for all prototypes.

I.e.: say I have B->A and I want to make it B->A->Base. Now instanceof should return true for A, B, Base.

I tried using B.prototype.prototype and Object.setPrototypeOf(), but no luck in either case.

Sample with Object.setPrototypeOf():

class A {                                                                                                                                                                                                                            
    do() { console.log("do A"); }                                                                                                                                                                                                                            
}                                                                                                                                                                                                                                    


class B extends A {
    do() { console.log("do B"); }

    doB() { console.log("what"); }
}


var a = new A();
var b = new B();

a.do();
b.do();
b.doB();

console.log(a instanceof A)  // true
console.log(a instanceof B)  // false

console.log(b instanceof A)  // true
console.log(b instanceof B)  // true


class Base {
    doBase() { console.log("this is the base!"); }
}


// now add Base to B's chain, so that B -> A -> Base
// TODO: doesn't work!

Object.setPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(B)), Base.prototype)
//Object.setPrototypeOf(Object.getPrototypeOf(B), Base)


console.log(Object.getPrototypeOf(B))
console.log(Object.getPrototypeOf(Object.getPrototypeOf(B)))


var c = new B();

console.log(c instanceof B)    // true
console.log(c instanceof A)    // true
console.log(c instanceof Base) // false (!!! how to fix it?)

c.doBase();                    // crash, not a function
MiB
  • 390
  • 1
  • 2
  • 15
  • 1
    That looks crazy. Why would you want to do that? Why don’t you just create an instance of `Base` and make it a subclass of `B`? That would be the point of OO. – idmean Sep 25 '16 at 20:08
  • "*I want to modify it so that the hierarchy is kept intact*" - that's impossible. `A` inherits from `Object` originally, and now it should inherit from `Base`? That's not "intact". – Bergi Sep 25 '16 at 20:20
  • Yes, crazy indeed. And not my choice. I am trying to fix an older framework so I can use it with ES6 classes. Said framework extends all user components with a Component base class, but does so by overwriting the prototype and then copying all original properties back into it. So it now looks like it inherited from Component, thereby losing the original class. Then I cannot instantiate it anymore because babel generates a `_classCallCheck` which now fails. – MiB Sep 25 '16 at 20:20
  • @Bergi: yes, exactly. Intact only up to that final Object that is supposed to be Base afterwards. Maybe view it as an insert into the chain, Object will still be there at the end. – MiB Sep 25 '16 at 20:21
  • So you want `a` to be `instanceof Base` as well? – Bergi Sep 25 '16 at 20:22
  • Why not just fix the framework itself to be compatible with ES6 instead of trying to hack around it? – Bergi Sep 25 '16 at 20:23
  • @Bergi: I don't care. If need be, I'm fine with that. As long as that is possible *after* the above code executed. – MiB Sep 25 '16 at 20:24
  • Afaik `_classCallCheck` fails because things are invoked without `new`, not because of the prototype chain. – Bergi Sep 25 '16 at 20:24
  • 1
    @MiB Actually I meant you should expose the framework's `Component` class so that you can write `class A extends myframework.Component` and the framework doesn't need to overwrite any prototypes any more – Bergi Sep 25 '16 at 20:25
  • Oh you guys... pfft. :) – TylerY86 Sep 25 '16 at 20:25
  • :D @Bergi: yeah, in an ideal world... but that would make the API incompatible with existing components, which all don't inherit directly from it. Plus, the framework does some internal initialization with Component, passing data to it that I don't have access to. It's really a bit crazy. And I only have the problem because I like ES6 classes :( No problem with LiveScript classes or CoffeeScript classes... – MiB Sep 25 '16 at 20:34
  • @MiB In an ideal world you'd also fix all the existing components to let them explicitly inherit :-) Can you maybe show us the code of the relevant framework parts you're using so that we can figure out why it doesn't work with ES6 classes? – Bergi Sep 25 '16 at 20:38
  • @Bergi: absolutely! See https://github.com/derbyjs/derby/blob/master/lib/components.js, function `extendComponent()` at the very end. – MiB Sep 25 '16 at 20:47
  • @MiB [O M G](https://github.com/derbyjs/derby/blob/master/lib/components.js#L79-L83). But actually [this line](https://github.com/derbyjs/derby/blob/master/lib/components.js#L202) suggests that `class A extends Component` should in fact work (without changing the framework). What exactly is the error you're getting when trying that? – Bergi Sep 25 '16 at 21:06
  • @Bergi: to be honest, I didn't even try it, but now that you mention it, I am also sure it would work! Because the init stuff happens later... stupid me. However, with TylerY86's answer, I now also successfully fixed extendComponent!!! :-) See here: https://gist.github.com/michael-brade/850123d084776ef01fcdaecfa542521e – MiB Sep 25 '16 at 21:15
  • @Bergi is right though. You shouldn't be hacking this in if you can implement it correctly. – TylerY86 Sep 29 '16 at 20:05

1 Answers1

3

This shows inheritance relationship from B to A;

console.log(Object.getPrototypeOf(B.prototype) === A.prototype);

So, given this;

class A { do() { console.log("do A"); } }

class B extends A {
  do() { console.log("do B"); }
  doB() { console.log("what"); }
}

class Base {
  doBase() { console.log("this is the base!"); }
}

Without referencing A, you need this;

Object.setPrototypeOf(Object.getPrototypeOf(B.prototype), Base.prototype);

Such that;

console.log(new A() instanceof Base); // true
console.log(new B() instanceof Base); // true
(new B()).doBase(); // this is the base!

How's that? Play with it here.

As mentioned by Bergi, to accomplish Object.setPrototypeOf(A,Base) in the same manner as class extension without referencing A, Object.setPrototypeOf(Object.getPrototypeOf(B.prototype.constructor), Base). (Need to investigate this, reported that it causes a cyclic inheritance error...)

Extended the test here to perform the class constructor inheritance as shown above. Works in Chrome 53 (V8 5.3.332.45).

Edit: Note, this is monkey-patching the inheritance chain. It's not a fantastic idea for performance.

TylerY86
  • 3,737
  • 16
  • 29
  • 1
    Don't forget `Object.setPrototypeOf(A, Base)` – Bergi Sep 25 '16 at 20:25
  • Why would I do that? – TylerY86 Sep 25 '16 at 20:26
  • Nice idea! but my problem is exactly that I don't know about A at that point. All I get is a reference to B, now I have to find out about A and then set the prototype. – MiB Sep 25 '16 at 20:29
  • Oh ok. No problem. – TylerY86 Sep 25 '16 at 20:29
  • @TylerY86 Because that's what `class A extends Base` does as well – Bergi Sep 25 '16 at 20:35
  • Why should the constructor of A extend the constructor of Base? – TylerY86 Sep 25 '16 at 20:39
  • @TylerY86: hey, that's great! Do you know the difference, too? That is, why does `Object.getPrototypeOf(Object.getPrototypeOf(B))` break, whereas `Object.getPrototypeOf(B.prototype)` works? – MiB Sep 25 '16 at 20:40
  • 1
    `Object.getPrototypeOf(B)` is `B.__proto__`, not the same as `B.prototype`. http://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript – TylerY86 Sep 25 '16 at 20:41
  • @Bergi You're right, it does do that. I'm not sure if it's effects are desired, but I'll mention it in the answer. `Object.getPrototypeOf(B) === A` is `true` – TylerY86 Sep 25 '16 at 20:46
  • I say "not sure if it's effects are desired" because the of the comments describing what would be "intact". Pretty sure this is just hackery and quickfix magic. I understand it's bad form, but cake and eating it something something... – TylerY86 Sep 25 '16 at 20:53
  • @TylerY86: awesome, that B.prototype thing fixed it all! Using your latest comment doesn't work, though: it gives me a "`TypeError: Cyclic __proto__ value at Function.setPrototypeOf`". I don't care, the other solution is great. – MiB Sep 25 '16 at 21:29
  • Are you doing it in Chrome or some other engine? I just tried it again, worked fine as far as I can tell. :\ Oh well, glad it's working out for you. – TylerY86 Sep 26 '16 at 02:33
  • Also tried in a few versions of Firefox, worked fine. Added test case (https://jsfiddle.net/Lnjjmpgk/7/) to answer. – TylerY86 Sep 26 '16 at 02:43