我想将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();
有人想知道它是否可行,即使可能不应该这样做?
答案 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
类扩展。 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
with a construct
handler的潜力,但由于缺少new.target
而无法使其正常工作。