🏭
Functionのサブクラス (JavaScript)
現代のJSで Function
コンストラクタを直接触ることはほとんどないと思いますが、隠された機能がちょっと面白いので本記事で説明します。
Function
コンストラクタ
JavaScriptの Function
関数は厳密にはクラスではありませんが、クラスのように使うことができます。
// typeof object === "function" とするべきだが、これでもほぼ同様に判定できる
if (object instanceof Function) {
// ...
}
// ソースコードから関数を生成
const f = new Function("x", "\"use strict\"; return x + 1");
console.log(f(42)); // => 43
// newが無くても同じ
const f = Function("x", "\"use strict\"; return x + 1");
Function
コンストラクタは eval
の比較的安全な代替としてよく使われます。
ユーザー定義サブクラス
ほとんど使う機会はありませんが、 Function
のサブクラスを作ることができます。
class MyFunction extends Function {
twice() {
this();
this();
}
}
const f = new MyFunction("console.log('Hello!');");
f.twice(); // 2回呼ばれる
組み込みのサブクラス
AsyncFunction
, GeneratorFunction
, AsyncGeneratorFunction
というサブクラス (正確にはクラスではない) が存在します。それぞれ Function
の直接のサブクラスです。
これらはグローバル変数として露出されていないため、以下のようにして抽出する必要があります。
const AsyncFunction = (async()=>0).constructor;
const GeneratorFunction = (function*(){}).constructor;
const AsyncGeneratorFunction = (async function*(){}).constructor;
以下のような構文が各クラスのインスタンスになっています。
- Functionの直接のインスタンス
- function式、function宣言
- class式、class宣言
- メソッド
- アロー関数式
- AsyncFunctionの直接のインスタンス
- async function式、async function宣言
- asyncメソッド
- asyncアロー関数式
- GeneratorFunctionの直接のインスタンス
-
function*
式、function*
宣言 - ジェネレーターメソッド
-
- AsyncGeneratorFunctionの直接のインスタンス
-
async function*
式、async function*
宣言 - asyncジェネレーターメソッド
-
これらのサブクラスのコンストラクタを使うと、awaitやyieldを含んだ処理のevalが可能です。
new AsyncFunction("await 1");
new GeneratorFunction("yield 1");
new AsyncGeneratorFunction("await yield");
こちらもユーザー定義のクラスに継承させることが可能です。
トランスパイラ対応
Babelなどのトランスパイラはasync functionやジェネレーターを通常の関数にトランスパイルすることができます。ターゲットブラウザによっては AsyncFunction
などのコンストラクタインスタンスを正しく取得できない可能性があります。もちろん、Webブラウザが実際にこれらの構文に対応していなければ根本的に不可能です。
対応方法のひとつとして、トランスパイルできないようにevalで括ってしまう方法が考えられます。
const AsyncFunction = Function("return (async()=>0).constructor")();
const GeneratorFunction = Function("return (function*(){}).constructor")();
const AsyncGeneratorFunction = Function("return (async function*(){}).constructor")();
もう1つの方法として生成後に値の正当性をチェックするというのが考えられます。
if (AsyncFunction.name !== "AsyncFunction") throw new Error("Not an AsyncFunction");
// あるいは
if (AsyncFunction === Function) throw new Error("Not an AsyncFunction");
Discussion