Some interesting case which I don't understand. I proxy classes and extend a child class from a proxied base class.
When a child is constructed inside the construct
trap for some reason a wrong prototype is assigned to an instance - the base class' prototype instead of a child class' prototype:
class prototype: Child [
"constructor",
"method",
"childMethod"
]
assigned prototype: Base [
"constructor",
"method"
]
This happens both in Chrome and Firefox. So it's not looking like a bug but rather everything to the spec. The problem I cannot understand why. The fix is to set the prototype manually (the commented line), but the mystery remains.
Could anyone explain why this happens:
const proxy = what => new Proxy(what, {
construct(_class, args, constructor) {
const obj = new _class(...args);
console.log('class prototype:', _class.name, Object.getOwnPropertyNames(_class.prototype));
console.log('assigned prototype:', obj.__proto__.constructor.name, Object.getOwnPropertyNames(obj.__proto__));
// for some reason we need this if a class is proxied
//Object.setPrototypeOf(obj, _class.prototype);
return obj;
}
});
const Base = proxy(class Base {
isBase = true;
method(){
console.log('Base method');
}
});
const Child = proxy(class Child extends Base { // extends from a proxy
isChild = true;
method() {
console.log('Child method');
super.method();
}
childMethod(){}
});
const base = new Base;
const child = new Child;
console.log('--------- EXECUTING METHODS ---------');
base.method();
child.method();
If we set the prototype manually everything works fine:
const proxy = what => new Proxy(what, {
construct(_class, args, constructor) {
const obj = new _class(...args);
// for some reason we need this if a class is proxied
Object.setPrototypeOf(obj, _class.prototype);
return obj;
}
});
const Base = proxy(class Base {
isBase = true;
method(){
console.log('Base method');
}
});
const Child = proxy(class Child extends Base { // extends from a proxy
isChild = true;
method() {
console.log('Child method');
super.method();
}
childMethod(){}
});
const base = new Base;
const child = new Child;
console.log('--------- EXECUTING METHODS ---------');
base.method();
child.method();
super()
is expected to set this
to an instance of the original (top-level) constructor it is called from, but this does not happen in your scenario. If in the constructor of Child
you do this:
constructor() {
super();
console.log(this instanceof Child);
}
You'll get as output false
. This happens because the proxy of the Base
constructor is invoked by super()
and it explicitly returns an instance of Base
without any clue that this was actually intended to be a Child
instance.
As already explained in comments, the correct execution of the original intent of super()
is to use Reflect.construct
with the third argument in your proxy handler. That handler gets a third argument that tells you what the intended type was of constructed instance:
construct(_class, args, constructor) {
return Reflect.construct(_class, args, constructor);
}
Now that super()
call will use that returned Child
instance and set this
to it.