JavaScript に於いて,関数は特殊なオブジェクトのため,普通にサブクラスすることはできません.そこで,普通に関数を作成して,その関数に対して必要な定義を行うこととなります.ここで,工夫して定義を行うと,普通のサブクラス可能な関数コンストラクタを定義することができることを発見いたしました.以下にそのコードを示します.
コード
なお,このコードは Apache 2.0 ライセンスで利用可能なものとします.
{
let _;
const ExtensibleFunction = function ExtensibleFunction(){return _(new.target)};
Reflect.setPrototypeOf(ExtensibleFunction, Function);
ExtensibleFunction.CALL = Symbol('[[Call]]');
ExtensibleFunction.CONSTRUCT = Symbol('[[Construct]]');
ExtensibleFunction.prototype = Object.create(Function.prototype);
ExtensibleFunction.prototype.constructor = ExtensibleFunction;
ExtensibleFunction.prototype.toString = function(){return"function anonymous() {\n [native code]\n}"};
ExtensibleFunction.prototype[Symbol.toStringTag] = 'ExtensibleFunction';
ExtensibleFunction.prototype[ExtensibleFunction.CALL] = function(thisArgument, argumentsList){};
ExtensibleFunction.toString = function(){return"function ExtensibleFunction() {\n [native code]\n}"};
_ = function (newTarget) {
let _;
const _this = function(...a){return _(this, a, new.target)};
delete _this.length;
delete _this.name;
Reflect.setPrototypeOf(_this, newTarget.prototype);
_ = function (thisArgument, argumentsList, newTarget) {
if ('undefined' == typeof newTarget) {
return _this[ExtensibleFunction.CALL](thisArgument, argumentsList);
} else if ('function' == typeof _this[ExtensibleFunction.CONSTRUCT]) {
return _this[ExtensibleFunction.CONSTRUCT](argumentsList, newTarget);
} else {
throw new TypeError('Not a constructor');
}
}
return _this;
};
globalThis.ExtensibleFunction = ExtensibleFunction;
}
使い方
クラスを作る
class MyClass extends ExtensibleFunction {
constructor(n) {
super();
this.prototype.number = +n;
}
[ExtensibleFunction.CALL]() {
throw new TypeError("class constructors must be invoked with 'new'");
}
[ExtensibleFunction.CONSTRUCT]() {
return {
_id: 'foo',
};
}
[Symbol.hasInstance](obj) {
return obj && obj._id === 'foo';
}
}
const myClass42 = new MyClass(42);
console.log((new myClass42) instanceof myClass42); // true
console.log((new myClass42).number); // 42
関数を作る
class MyFunction extends ExtensibleFunction {
constructor(value) {
super();
this.value = value;
}
[ExtensibleFunction.CALL]() {
return this.value;
}
}
const myFunction42 = new MyFunction(42);
console.log(myFunction42()); // 42
new myFunction42; // TypeError
総括
この様にして,自由自在に関数オブジェクトを動的に作ることができましょう.ご意見等 お待ちしております.