関数を作るもう1つの方法があります。ほとんど使われませんが、代替手段がないことがたまにあります。
構文
関数を作る構文です:
let
func =
new
Function
(
[
arg1[
,
arg2[
,
...
argN]
]
,
]
functionBody)
引数 arg2...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 =
...
サーバから動的にコードを受け取る ...
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 は未定義
通常の振る舞いとの比較です:
function
getFunc
(
)
{
let
value =
"test"
;
let
func
=
function
(
)
{
alert
(
value)
;
}
;
return
func;
}
getFunc
(
)
(
)
;
// "test", getFunc のレキシカル環境から
この new Function
の特殊な機能は奇妙に見えますが、実践では非常に役立ちます。
本当に文字列から関数を作る必要がある場合をイメージしてください。その関数のコードはスクリプト生成時には知られていません(そういう訳で通常の関数を使うことができません)が、実行中に認識されます。我々はサーバや別のソースからそれを受け取ることができます。
新しい関数はメインのスクリプトと相互にやり取りが必要です。
外部変数へアクセスしたいときはどうするのでしょうか?
問題はJavaScriptが本番環境に公開される前に、minifier(余分なコメントやスペースなどを削除することでコード小さくする特別なプログラム)を使用して圧縮されており、重要なことはローカル変数をより短いものにリネームします。
例えば、もし関数が let userName
を持っていたとき、minifier はそれを let a
(または既に使われていれば別の文字) に置き換え、その変数を使用します。変数はローカルであり関数の外部からアクセスすることはできないため、それは通常安全です。また、関数の内側では minifier はそれに関する全ての箇所を置き換えます。Minifiers は賢いので、単なる検索と置換ではなく、コード構造を分析するので問題ありません。
そのため、new Function
が外部変数へアクセスする場合、リネームされた userName
を見つけることはできません。
new Function
が外部変数へアクセスする場合、minifiers で問題になります。
加えて、このようなコードはアーキテクチャ的に良くなく、エラーになりやすいです。
new Function
として作成された関数に何かを渡すには、その引数を使用しなければなりません。
サマリ
構文:
let
func =
new
Function
(
[
arg1,
arg2,
...
argN]
,
functionBody)
;
歴史的な理由から、引数はカンマ区切りのリストで与えられます。
これらの3つの意味は同じです:
new
Function
(
'a'
,
'b'
,
' return a + b; '
)
;
// 基本構文
new
Function
(
'a,b'
,
' return a + b; '
)
;
// カンマ区切り
new
Function
(
'a , b'
,
' return a + b; '
)
;
// スペースありのカンマ区切り
new Function
で作られた関数は グローバルレキシカル環境を参照する [[Environment]]
を持っており、外部のレキシカル環境ではありません。従って、それらは外部の変数を使うことができません。ですが、それは良いことです。明示的なパラメータ渡しはアーキテクチャ的にはよりよい方法であり、minifierの問題も起きません。
コメント
<code>
タグを使ってください。複数行の場合は<pre>
を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。