如何模拟Javascript“ super”,以便定义“ new.target”?

时间:2019-01-29 05:11:44

标签: javascript ecmascript-6

我尝试不使用ES6关键字(例如,不使用classsuperextends来模仿此javascript):

class Foo {
  constructor() {
    if (!new.target) 
      console.log('Foo() must be called with new');
  }
}

class Bar extends Foo {
  constructor() {
    super(...arguments);
  }
}

var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo); // true

我已经走了这么远,但它们并不相等。以下抛出(我登录),而后者则不抛出:

function Foo() {
  if (!new.target) 
      console.log('Foo() must be called with new');
}

function Bar() {
  Foo.apply(this, arguments)
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;

var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);

那么,当我从new.target调用Foo时如何模拟为Bar提供值?


因此,似乎没有applycall允许传递new.target。我想这会违背new.target的目的(尽管JS中的所有内容都是公开的这一事实确实吸引了我)。

因此,要在ES5中进行仿真,我们需要添加一些内容。

以下答案中的一个解决方案分配了一个新对象。

此解决方案添加了新功能construct,这些功能可以在ES5中照常链接,并使该功能本身可以做任何事情,仅需检查其是否用作构造函数即可。

function Foo() {
  if (!new.target) 
    throw 'Foo() must be called with new';
  console.log('Foo new check');
  Foo.prototype.construct.apply(this, arguments);
}
Foo.prototype.construct = function() {
  console.log('Foo construction logic');
}

function Bar() {
  if (!new.target) 
    throw 'Bar() must be called with new';
  console.log('Bar new check');
  Bar.prototype.construct.apply(this, arguments);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
Bar.prototype.construct = function() {
  // super()
  Foo.prototype.construct.apply(this, arguments);

  console.log('Bar construction logic');
}

var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);

最重要的是,似乎ES6功能不是不是,只是ES5上的语法糖。当然,他们可以只添加一个Function.prototype.super(target, arguments, newTarget),然后我们就可以使用它。我希望他们能做到!


只有super可以使用Javascript调用函数,并且无法立即使this可用。因此super是唯一的。 super只能在constructor的上下文中调用,而class只能在super的上下文中使用。因此,所有这些关键字对于使this都是必需的。因此,Javascript引入了非常具体的面向对象功能。看起来像在“原型”概念之上构建语言是有局限性的。

这是可耻的...

我想知道为什么javascript突然决定强制执行这个不变式。 super在调用super之前不可用。为什么不把BaseType.prototype.constructor.call(this, ...)简化为super呢?为什么不让它被多次调用?我们可以用Javascript的许多其他方式来解决问题,为什么现在就开始执行呢?

反正...

因此,双重底线是,存在一个早期绑定的Javascript调用foo.bar(),它没有等效的后期绑定(例如,bar.call('foo')可以通过{{ 1}})。

4 个答案:

答案 0 :(得分:4)

使用Object.assign将父构造函数的new Foo(...arguments)分配给实例:

function Foo(arg) {
  if (!new.target) 
    throw 'Foo() must be called with new';
  this.arg = arg;
  this.fooProp = 'fooProp';
}

function Bar() {
  Object.assign(
    this,
    new Foo(...arguments)
  );
  this.barProp = 'barProp';
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;

var bar = new Bar('abc');
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);
console.log(bar);

但是new Foo(...arguments)是ES6语法。要将其翻译为ES5,请使用

new (Function.prototype.bind.apply(Foo, [null, ...arguments]))()

(负责new部分),然后再次转换为

new (Function.prototype.bind.apply(Foo, [null].concat(Array.prototype.slice.call(arguments))))()

function Foo(arg) {
  if (!new.target) 
    throw 'Foo() must be called with new';
  this.arg = arg;
  this.fooProp = 'fooProp';
}

function Bar() {
  Object.assign(
    this,
    new (Function.prototype.bind.apply(Foo, [null].concat(Array.prototype.slice.call(arguments))))()
  );
  this.barProp = 'barProp';
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;

var bar = new Bar('abc');
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);
console.log(bar);

答案 1 :(得分:0)

ES6类语法不是ES5的语法糖,但 却是其他ES6功能的语法糖。

class Bar extends Foo {
  constructor() {
    super(...arguments);
  }
}

非常类似于

let Bar = function(...args) {
  const _this = Reflect.construct(Object.getPrototypeOf(Bar), args, new.target);
  return _this;
};
Object.setPrototypeOf(Bar, Foo);
Object.setPrototypeOf(Bar.prototype, Foo.prototype);

其中Reflect.construct使用给定的new.target值构造和对象,并使用一组参数调用给定的构造函数。

答案 2 :(得分:0)

要编写抽象类,您需要确保在被调用类的原型的上下文中调用父类的构造函数。 为此,ES6引入了“ Reflect.construct”。

对于ES5,您可以实现自己的“拐杖”。

// Reflect.construct simulation for ES5
class Reflect2 {
    static construct(TargetClass,args,ProtoClass){
        let oldProto= TargetClass.prototype;
        let desc=Object.getOwnPropertyDescriptor(TargetClass,'prototype');
        if(desc.hasOwnProperty('value') && desc.writable===false){
            if(desc.configurable===true){
                desc.writable=true;
                Object.defineProperty(TargetClass,'prototype',desc);
            }
        }
        TargetClass.prototype=ProtoClass.prototype;
        let self=new (Function.prototype.bind.apply(TargetClass,args));
        TargetClass.prototype=oldProto;
        return self;
    }
}
function ParentClass(){
}
ParentClass.prototype.constructor=ParentClass;
function MyClass(){
    if(new.target===undefined){throw Error();}
    let self=Reflect2.construct (ParentClass,[],new.target);
    // your code
    return self;
}
MyClass.prototype=Object.create(ParentClass.prototype,{
    constructor:{
        value:MyClass
    }
});
let a=new MyClass();
console.log(Object.getPrototypeOf(a)===MyClass.prototype);

ES6中的“ super”与“ this”相同,只是在父原型的上下文中。例如,当我们通过“ bind()”将对象绑定到函数时,此处仅将对象“ this”绑定到原型对象。 “ super”可以通过两种方式实现:

  • 通过原型扫描,并绑定到“ this”对象的方法和反应特性;
  • 通过代理对象

ES6中的仿真“超级” 通过代理对象

/**
* @param {object} self - this object  
* @param {function} ProtoClass - the class to observe.  
 Usually the current class for which methods are being written.  
* @param {function} [new_target]  used in the constructor.   
If not passed, then the default will be the prototype constructor for "self" 
 (Object.getPrototyprOf(self).constructor).
* @param {boolean} [bindSelf] - if true, then the methods and reactive properties 
 of the parent will work in the context of self, if false, then the methods and  
reactive properties will work within the framework of the object in which they  
are called (i.e. within the Proxy object).
*/
function Super(self,ProtoClass,new_target,bindSelf=false){
    let parentProto=Object.getPrototypeOf(ProtoClass.prototype);
    let descs={};
    // collect descriptors of all prototypes
    do {
        let buf=Object.getOwnPropertyDescriptors(parentProto);
        for(let prop of Object.keys(buf)){
            if(!Object.prototype.hasOwnProperty.call(descs,prop)){
                descs[prop]=buf[prop];
            }
        }          
        parentProto=Object.getPrototypeOf(parentProto);
    }while (parentProto!==null); 

    // we define the control object
    let answer={};
    let new_obj=function (...args){
       let ParentClass=Object.getPrototypeOf(ProtoClass.prototype).constructor;
       if(ParentClass===Object || ProtoClass===ParentClass){
            return self;
       }
       new_target=new_target??Object.getPrototypeOf(self).constructor;
       return Reflect.construct(ParentClass,args,new_target);
    } 
    
    let parent=new Proxy(self,{
        get(target,prop){
            let desc=descs[prop];
            if(desc===undefined){
                return;
            }
            if(desc.hasOwnProperty('value')){
                if(bindSelf && typeof desc.value==='function'){
                    return desc.value.bind(target);
                }
                return desc.value;
            } else
            if(desc.hasOwnProperty('get')){
                return desc.get.call(target);
            }
        },
        set(target,prop,value){
            let desc=descs[prop];
            if(desc===undefined || desc.hasOwnProperty('value')){
                target[prop]=value;
                return true;
            }
            if(desc.hasOwnProperty('set')){
                desc.set.call(target,value);
                return true;
            } 
        }
    });
    Object.defineProperties(answer,{
        new:{
            value:new_obj
        },
        parent:{
            value:parent
         }
    });
    return answer;
}

使用方法

function A(...args){
}
A.prototype.method=function(){
    console.log('hello',this);
    return this;
}
A.prototype.constructor=A;
function B(...args){
    if(new.target===undefined){throw Error();}
    let _super=Super(this,B,new.target);
    let self= _super.new(...args);
    let b=_super.parent.method(); // return proxy object
    /*
        in this case, all execution inside the method will occur as if this has a parent prototype.
    */
    console.log(b===this);// false  
    return self;
}
B.prototype=Object.create(A.prototype);
B.prototype.constructor=B;
B.prototype.method=function(){
    console.log('bay');
    return this;
}
let b=new B();

function A(...args){
}
A.prototype.method=function(){
    console.log('hello',this);
    return this;
}
A.prototype.constructor=A;
function B(...args){
    if(new.target===undefined){throw Error();}
    /*
    we want the methods to execute against the "this" context 
    and not in the "new Proxy ('this', {})" context
    */
    let _super=Super(this,B,new.target,true);// arg[3] true
    let self= _super.new(...args);
    let b=_super.parent.method(); // return this object
    console.log(b===this);// true  
    return self;
}
B.prototype=Object.create(A.prototype);
B.prototype.constructor=B;
B.prototype.method=function(){
    console.log('bay');
    return this;
}
let b=new B();

对于ES5代理,可以用常规对象替换,其中“ this”到“ bind”绑定到方法。

function Super(self,ProtoClass,new_target){
    let parentProto=Object.getPrototypeOf(ProtoClass.prototype);
    let parent={};
    // properties all prototypes
    do {
        let buf=Object.getOwnPropertyDescriptors(parentProto);
        for(let prop of Object.keys(buf)){
            if(!Object.prototype.hasOwnProperty.call(parent,prop)){
                let desc=buf[prop];
                if(buf[prop].hasOwnProperty('get') ){
                    desc.get=buf[prop].get.bind(self);
                }
                if(buf[prop].hasOwnProperty('set') ){
                    desc.set=buf[prop].set.bind(self);
                }
                if(buf[prop].hasOwnProperty('value') ){
                    if (typeof buf[prop].value === 'function') {
                         desc.value=buf[prop].value.bind(self);
                    } else{
                        delete desc.value;  
                        delete desc.writable;
                        desc.get=function (){
                            return buf[prop];
                        };
                        desc.set=function(v){
                            self[prop]=v;
                        };
                    }
                }
                Object.defineProperty(parent,prop,desc);
            }
        }          
        parentProto=Object.getPrototypeOf(parentProto);
    } while (parentProto!==null); 

    // we define the control object
    let answer={};
    let new_obj=function (){
       let ParentClass=Object.getPrototypeOf(ProtoClass.prototype).constructor;
       if(ParentClass===Object || ProtoClass===ParentClass){
            return self;
       }
       new_target=new_target??Object.getPrototypeOf(self).constructor;
       // We described the "Reflect.construct method" above. see Reflect2.construct
       return Reflect.construct(ParentClass,Array.prototype.slice.call(arguments),new_target);
    } 
    Object.defineProperties(answer,{
        new:{
            value:new_obj
        },
        parent:{
            value:parent
         }
    });
    return answer;
}

示例

function A(...args){
}
A.prototype.method=function(){
    console.log('hello',this);
    return this;
}
A.prototype.constructor=A;
function B(...args){
    if(new.target===undefined){throw Error();}
    let _super=Super(this,B,new.target);
    let self= _super.new(...args);
    let b=_super.parent.method();
    console.log(b===this);
    return self;
}
B.prototype=Object.create(A.prototype);
B.prototype.constructor=B;
B.prototype.method=function(){
    console.log('bay');
    return this;
}
let b=new B();

答案 3 :(得分:0)

使用 if (!new.target) 的目的是检查 Foo 是否使用 new(作为构造函数)调用,在这种情况下,我们可以使用 if(this instanceof Foo) 代替。

this 可以是 FooBar 的实例,无论您调用 new Foo() 还是 Foo.apply(this)this instanceof Foo 始终可以返回true 而如果您只调用 false(没有 Foo()new/apply),它将返回 call

试试这个:

function Foo() {
  if (!this instanceof Foo) 
      console.log('Foo() must be called with new');
}

function Bar() {
  Foo.apply(this, arguments)
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;

var bar = new Bar();
var barIsFoo = bar instanceof Foo;
console.log(barIsFoo);