一个代理的JS子类给实例分配了一个错误的原型。

huangapple go评论70阅读模式
英文:

A proxied JS child class assigns a wrong prototype to an instance

问题

我是你的中文翻译助手,以下是翻译好的内容:

有一个我不理解的有趣案例。
我代理类并从代理的基类扩展一个子类。

当一个子类在construct陷阱中被构造时,由于某种原因,一个错误的原型被分配给实例 - 基类的原型而不是子类的原型:

class prototype: Child [
  "constructor",
  "method",
  "childMethod"
]
assigned prototype: Base [
  "constructor",
  "method"
]

这在Chrome和Firefox中都会发生。所以这看起来不像是一个bug,而是符合规范的一切。我无法理解的问题是为什么会发生这种情况。修复方法是手动设置原型(注释掉的那一行),但是这个谜团仍然存在。

有人能解释一下为什么会发生这种情况吗:

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();

如果我们手动设置原型,一切都正常:

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();
英文:

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:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const proxy = what =&gt; new Proxy(what, {

    construct(_class, args, constructor) {

        const obj = new _class(...args);
        
        console.log(&#39;class prototype:&#39;, _class.name, Object.getOwnPropertyNames(_class.prototype));
        console.log(&#39;assigned prototype:&#39;, 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(&#39;Base method&#39;);
    }
});

const Child = proxy(class Child extends Base { // extends from a proxy

    isChild = true;
    method() {
        console.log(&#39;Child method&#39;);
        super.method();
    }
    childMethod(){}

});

const base = new Base;
const child = new Child;

console.log(&#39;--------- EXECUTING METHODS ---------&#39;);
base.method();
child.method();

<!-- end snippet -->

If we set the prototype manually everything works fine:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const proxy = what =&gt; 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(&#39;Base method&#39;);
    }
});

const Child = proxy(class Child extends Base { // extends from a proxy

    isChild = true;
    method() {
        console.log(&#39;Child method&#39;);
        super.method();
    }
    childMethod(){}

});

const base = new Base;
const child = new Child;

console.log(&#39;--------- EXECUTING METHODS ---------&#39;);
base.method();
child.method();

<!-- end snippet -->

答案1

得分: 2

super()预期将this设置为调用它的原始(顶级)构造函数的实例,但在您的情况下并没有发生这种情况。如果在Child的构造函数中执行以下操作:

constructor() {
    super();
    console.log(this instanceof Child);
}

你将得到输出false。这是因为super()调用了Base构造函数的代理,并且它明确返回一个Base实例,没有任何线索表明这实际上是一个Child实例。

如前所述,在代理处理程序中正确执行super()的原始意图是使用Reflect.construct和第三个参数。该处理程序获得第三个参数,该参数告诉您构造的实例的预期类型:

construct(_class, args, constructor) {
    return Reflect.construct(_class, args, constructor);
}

现在,super()调用将使用返回的Child实例并将this设置为它。

英文:

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.

huangapple
  • 本文由 发表于 2023年8月9日 17:27:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76866343.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定