有没有办法让类构造函数像普通函数一样被调用?

时间:2019-06-16 17:57:41

标签: javascript ecmascript-6 new-operator mixins es6-class

我想将ES6类构造函数用作混合函数。

我有一个带有一些方法的简单类。简化示例:

class foo {
    constructor() {}

    hi() { console.log('hello'); }
}

在大多数情况下,我创建此类的实例:

let bar = new foo();
bar.hi(); // hello

有时它被用作超类:

class baz extends foo {
    constructor() { super(); }
}

let bar = new baz();
bar.hi(); // hello

但是,由于这些方法可以独立于其他任何方法工作,因此,如果我可以将构造函数用作mixin,那就很好了,

class foo {
    constructor( mixin ) {
        if ( !new.target ) { // not called with `new`
            if ( !mixin ) throw new TypeError( 'Must supply mixin' );
            mixin.hi = foo.prototype.hi;
            return mixin;
        }
    }

    // ...
}

let bar = {}; // this could be instance of another class, whatever.
foo( bar ); // Class constructor foo cannot be invoked without 'new'
bar.hi();

这就是我遇到的问题。构造函数不能像普通函数一样被调用。

是否有任何方法可以阻止构造器在不使用new的情况下调用时抛出错误,而无需恢复使用构建类的ES5方法?

我尝试将类包装为普通函数,并使用new.target(我的环境是ES6)来检测何时使用new

function Goo( mixin ) {
   if ( new.target ) return new foo();
   if ( !mixin ) throw new TypeError( 'Must supply mixin' );
   mixin.hi = foo.prototype.hi;
   return mixin;
}

let goo = new Goo(); // I get a new instance of foo
goo.hi(); // hello

let bar = {};
Goo( bar );
bar.hi(); // hello

...但是很快意识到:

class oof extends Goo { // ugh

此外,我还必须将static的内容从foo克隆到Goo,这是meh。

作为备用,我目前在mixin()类上使用静态foo方法:

class foo {
   static mixin( target ) {
      if ( !target ) throw new TypeError( 'Must supply target' );
      target.hi = foo.prototype.hi;
      return target;
   }
   // ...
}

let bar = {};
foo.mixin( bar ); // Well, it's semantic at least
bar.hi(); // hello

但是我渴望,即使只是看是否能够做到,在某些情况下也能在以下三种情况下起作用:

let ifoo = new foo();
ifoo.hi();

class woo extends foo { /* ... */ }
let iwoo = new woo();
iwoo.hi();

let bar = {};
let ibar = foo( bar );
ibar.hi();

有人想知道它是否可行,即使可能不应该这样做?

1 个答案:

答案 0 :(得分:0)

基于snippet posted by Bergi,我想到了以下功能:altEgo()

它将创建一个mask函数,其后是一个alt(用于正常调用)和ego(用于new实例)。它检查new.target以确定是使用alt()还是new ego()。您也可以extends mask

// to add an alter ego to fooFn:
// fooFn = altEgo( altFn, fooFn );

function altEgo( alt, ego ) {

    if ( typeof alt !== 'function' ) throw new TypeError( `alt must be a function` );
    if ( typeof ego !== 'function' ) throw new TypeError( `ego must be a function` );

    const mask = function( ...args ) {
        return new.target ? Reflect.construct( ego, args, new.target ) : alt( ego, mask, ...args );
    }

    for ( const property of Object.getOwnPropertyNames( ego ) )
        if ( altEgo.always.has( property ) || !mask.hasOwnProperty( property ) )
            Object.defineProperty( mask, property, Object.getOwnPropertyDescriptor( ego, property ) );

    return mask;
}
altEgo.always = new Set([ 'name', 'length', 'prototype' ]); // properties to copy to mask

这在OP末尾提到的所有三种情况下均可成功使用。

这是一个测试平台,其中ego函数为class foo。如您所见,带遮罩的foo就像原始类一样工作,甚至可以被woo类扩展。 foowoo的实例的行为符合预期,并且静态方法也起作用。如果您像普通函数一样调用foo,则alt会踢进来。

'use strict';

class foo {
    static chk() {
        console.log(`static ${this.name}.chk()`);
    }

    static get getchk() {
        console.log(`static ${this.name}.getchk`);
    }

    constructor( a, b, c ) {
        this.con = true;
        console.log(`new ${new.target.name}()`);
    }

    check() {
        console.log(`${this.constructor.name} inst.check(); inst.con = `+this.con);
    }

    get getcheck() {
        console.log(`${this.constructor.name} inst.getcheck`);
    }
}

console.dir( foo );

function altEgo( alt, ego ) {

    if ( typeof alt !== 'function' ) throw new TypeError( `alt must be a function` );
    if ( typeof ego !== 'function' ) throw new TypeError( `ego must be a function` );

    const mask = function( ...args ) {
        return new.target ? Reflect.construct( ego, args, new.target ) : alt( ego, mask, ...args );
    }

    for ( const property of Object.getOwnPropertyNames( ego ) )
        if ( altEgo.always.has( property ) || !mask.hasOwnProperty( property ) )
            Object.defineProperty( mask, property, Object.getOwnPropertyDescriptor( ego, property ) );

    return mask;
}
altEgo.always = new Set([ 'name', 'length', 'prototype' ]); // properties to copy to mask

let alt = function( ego, mask, target ) {
    console.log( 'alt function' );
    for ( const property of alt.clone )
        Object.defineProperty( target, property, Object.getOwnPropertyDescriptor( ego.prototype, property ) );
    return target;
}
alt.clone = new Set([ 'check', 'getcheck' ]);

foo = altEgo( alt, foo );

console.dir( foo );

console.log('foo =====================================');

foo.chk();
void foo.getchk;
let ifoo = new foo;
ifoo.check();
void ifoo.getcheck;

console.log('woo =====================================');

class woo extends foo {
    constructor() {
        super();
    }
}

woo.chk();
void woo.getchk;
let iwoo = new woo;
iwoo.check();
void iwoo.getcheck;

console.log('bar =====================================');

let ibar = foo( {} );
ibar.check();
void ibar.getcheck;

注意:我还研究了使用Proxy with a construct handler的潜力,但由于缺少new.target而无法使其正常工作。