JS代理子类实例原型分配异常:继承代理基类时为何原型错误?
类Proxy代理后子类继承的原型分配问题解析
问题现象
对类进行Proxy代理后,子类继承被代理的基类,在construct陷阱中构造子类实例时,实例的原型被错误分配为基类的prototype,而非子类自身的prototype。控制台输出如下:
class prototype: Child [ "constructor", "method", "childMethod" ] assigned prototype: Base [ "constructor", "method" ]
该现象在Chrome、Firefox中均存在,符合ECMAScript规范,手动通过Object.setPrototypeOf设置原型可修复,但需要明确触发原因。
核心原因
问题出在Proxy的construct陷阱实现逻辑上,结合ES规范的构造函数执行机制,具体如下:
- 执行
new Child()时,触发Child代理的construct陷阱,陷阱的第三个参数constructor代表实际的new.target(即Child的代理对象)。 - 当前代码中用
new _class(...args)创建实例时,new.target被强制设置为原始的Child类,而非传入的constructor参数。 - Child类继承自Base代理,其构造函数会自动调用
super(),进而触发Base代理的construct陷阱。 - Base代理的
construct陷阱中,new _class(...args)会创建一个Base类的实例(而非基于子类的new.target创建),并将该实例作为super()的返回值返回给子类构造函数。 - 根据ES规范,子类构造函数中
super()的返回值会成为实例的this,若子类无显式返回对象,最终返回的就是这个Base实例,导致实例原型被设为Base.prototype。
简单来说:错误的construct陷阱实现没有尊重new.target的传递,导致子类实例被替换成了基类实例。
正确修复方案
方案1:使用Reflect.construct(推荐)
在construct陷阱中,用Reflect.construct替代new _class(...args),它会保留传入的constructor作为new.target,确保实例基于正确的目标类创建:
const proxy = what => new Proxy(what, { construct(_class, args, constructor) { // 使用Reflect.construct尊重new.target const obj = Reflect.construct(_class, args, constructor); console.log('class prototype:', _class.name, Object.getOwnPropertyNames(_class.prototype)); console.log('assigned prototype:', obj.__proto__.constructor.name, Object.getOwnPropertyNames(obj.__proto__)); return obj; } });
方案2:手动设置原型(临时修复)
如问题中所示,手动通过Object.setPrototypeOf将实例原型强制设置为当前类的prototype,覆盖错误的原型:
const proxy = what => new Proxy(what, { construct(_class, args, constructor) { const obj = new _class(...args); // 强制修正原型 Object.setPrototypeOf(obj, _class.prototype); return obj; } });
复现代码
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();
内容的提问来源于stack exchange,提问作者Alexander Nenashev
相关产品推荐
相关产品推荐

