Есть ли способ разрешить вызов конструктора класса, как если бы это была обычная функция? - PullRequest
2 голосов
/ 16 июня 2019

Я бы хотел использовать конструктор класса 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

Но,поскольку методы могут работать независимо от чего-либо еще, было бы неплохо, если бы я мог использовать конструктор в качестве миксина, например так:

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 (мой env - 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, то есть Мех.

Как запасной вариант я в настоящее времяиспользуйте статический метод 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 голосов
/ 17 июня 2019

Основываясь на фрагменте , опубликованном Берги , я предложил следующую функцию: 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.

Вот тестовый стенд, в котором функция egoclass foo.Как видите, маска foo работает так же, как и оригинал, когда она используется в качестве класса, и может даже расширяться классом woo.Экземпляры foo и woo ведут себя как ожидалось, и статика также работает.Если вы вызываете 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 с обработчиком construct но не смог заставить его работать из-за отсутствия new.target.

...