2020年10月22日

「new Function」構文

関数を作成するもう一つの方法があります。めったに使用されませんが、他に選択肢がない場合があります。

構文

関数を作成する構文

let func = new Function ([arg1, arg2, ...argN], functionBody);

この関数は、引数arg1...argNと、指定されたfunctionBodyで作成されます。

例を見るとより理解しやすいでしょう。以下は2つの引数を持つ関数です。

let sum = new Function('a', 'b', 'return a + b');

alert( sum(1, 2) ); // 3

そして、以下は関数本体のみで、引数を持たない関数です。

let sayHi = new Function('alert("Hello")');

sayHi(); // Hello

これまで見てきた他の方法との主な違いは、関数が実行時に渡される文字列から文字通りに作成されることです。

以前のすべての宣言では、プログラマーである私たちがスクリプトに関数コードを記述する必要がありました。

しかし、new Functionを使用すると、任意の文字列を関数に変えることができます。たとえば、サーバーから新しい関数を受け取り、それを実行できます。

let str = ... receive the code from a server dynamically ...

let func = new Function(str);
func();

これは、サーバーからコードを受け取ったり、複雑なWebアプリケーションでテンプレートから関数を動的にコンパイルしたりする場合など、非常に特定のケースで使用されます。

クロージャ

通常、関数は特別なプロパティ[[Environment]]でどこで生まれたかを記憶します。これは、それが作成されたレキシカル環境を参照します(これは「変数スコープ、クロージャ」の章で取り上げました)。

しかし、関数がnew Functionを使って作成されるとき、その[[Environment]]は、現在のレキシカル環境ではなく、グローバルなレキシカル環境を参照するように設定されます。

したがって、そのような関数は外部変数にアクセスできず、グローバル変数にのみアクセスできます。

function getFunc() {
  let value = "test";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // error: value is not defined

通常の動作と比較してみましょう。

function getFunc() {
  let value = "test";

  let func = function() { alert(value); };

  return func;
}

getFunc()(); // "test", from the Lexical Environment of getFunc

new Functionのこの特別な機能は奇妙に見えますが、実際には非常に便利です。

文字列から関数を作成する必要があることを想像してみてください。その関数のコードは、スクリプトを書く時点では不明です(そのため、通常の関数を使用しません)。しかし、実行プロセスでわかります。サーバーや他のソースから受け取ることができます。

新しい関数は、メインスクリプトとやり取りする必要があります。

もしそれが外部変数にアクセスできたらどうなるでしょう?

問題は、JavaScriptが本番環境に公開される前に、ミニファイアを使って圧縮されることです。ミニファイアとは、余分なコメントやスペースを削除し、さらに重要なことに、ローカル変数をより短い名前に変更することでコードを縮小する特別なプログラムです。

たとえば、関数にlet userNameがある場合、ミニファイアはそれをlet a(またはこれが占有されている場合は別の文字)に置き換え、それをいたるところで行います。これは通常、変数がローカルであり、関数外の何もそれにアクセスできないため、安全なことです。そして、関数内では、ミニファイアはそれへの言及をすべて置き換えます。ミニファイアは賢く、コード構造を分析するため、何も壊しません。彼らは単なる単純な検索と置換ではありません。

したがって、new Functionが外部変数にアクセスできる場合、名前が変更されたuserNameを見つけることができなくなります。

もしnew Functionが外部変数にアクセスできると、ミニファイアとの間で問題が発生します。

また、そのようなコードはアーキテクチャ的に悪く、エラーが発生しやすくなります。

new Functionとして作成された関数に何かを渡すには、その引数を使用する必要があります。

まとめ

構文

let func = new Function ([arg1, arg2, ...argN], functionBody);

歴史的な理由により、引数はカンマ区切りのリストとして渡すこともできます。

これら3つの宣言は同じ意味です。

new Function('a', 'b', 'return a + b'); // basic syntax
new Function('a,b', 'return a + b'); // comma-separated
new Function('a , b', 'return a + b'); // comma-separated with spaces

new Functionで作成された関数は、外部ではなくグローバルなレキシカル環境を参照する[[Environment]]を持ちます。したがって、外部変数を使用できません。しかし、それは実際に良いことです。なぜなら、それが私たちをエラーから守ってくれるからです。明示的にパラメータを渡すことは、アーキテクチャ的にはるかに優れた方法であり、ミニファイアで問題が発生することはありません。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善すべき点に関する提案がある場合は、コメントする代わりに、GitHub issueを送信するか、プルリクエストをしてください。
  • 記事の内容で理解できないことがあれば、詳細に説明してください。
  • コードを数語挿入するには<code>タグを使用し、数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックスを使用してください(plnkrjsbincodepen…)。