0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScriptでサブクラス可能なFunctionクラスを作る

Last updated at Posted at 2021-05-12

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

総括

この様にして,自由自在に関数オブジェクトを動的に作ることができましょう.ご意見等 お待ちしております.

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?