
JavaScript Patterns 2章ではevalの代替としてFunctionコンストラクタを使う方法が紹介されています。Functionコンストラクタは、普通にJavaScriptを書いている分にはなかなか使うことがないので、馴染みがない人も多いだろうと思います。そこで関数式との比較を中心に簡単にまとめておきます。
Functionコンストラクタは、関数オブジェクト(つまりJavaScriptにおける関数)を作るためのコンストラクタです。文字列をJavaScriptコードとして扱えるという点ではevalにも似ていますが、戻り値は普通の関数なので、その他の方法で作った関数と全く同じ使い方ができます。
var funcObj = new Function('x', 'y', 'console.log(x + y);');
//普通に呼び出す
funcObj(1,2); // 3
//コンストラクタとして使ってみる
funcObj.prototype.hello = function(){ console.log('World'); };
(new funcObj(4,5)).hello(); // 'World'
ちなみにコンストラクタではなく関数として呼び出した場合は、コンストラクタとして呼び出した場合と同じ結果になります(仕様上そう規定されています)。よって以下の2つは同じ意味になります。
var funcObj1 = Function('x', 'y', 'console.log(x + y);'),
funcObj2 = new Function('x', 'y', 'console.log(x + y);');
この形、関数式にnew演算子を適用した場合と見た目が似ていますが、全く意味が違うということに注意してください。
// 関数式で関数を作る
var funcObj = function(){}
// 関数式で作った関数をコンストラクタとして呼び出す
var newlyCreatedObject = new function(){};
varの解釈について
Functionコンストラクタで作った関数においても、関数本体の中でvarステートメントを使って宣言した変数はローカル変数になります。
var a = 100,
func1 = new Function('var b = 200; return b;'),
func2 = function(){ var c = 300; return c; };
console.log(a); // 100
console.log(func1()); // 200
console.log(func2()); // 300
console.log(typeof b); //undefined
console.log(typeof c); //undefined
変数の生成(instantiation)は関数が呼び出されたときに実行されるプロセスなので、どのような方法で作った関数でも同じです。
スコープについて
関数式で作った関数は、関数式の外側のローカル変数を参照できます。しかしFunctionコンストラクタで作った関数はグローバル変数しか参照することができません。
//JavaScript Patterns Chapter2の例の改変
(function(){
var local = 1;
(function(){ local = 3; console.log(local); }()); // 3
console.log(local); // 3
}());
(function(){
var local = 1;
Function("console.log(typeof local)")(); //undefined
}());
言い換えると、関数式で作った関数オブジェクトが「関数式が評価された時点でのスコープの状態」を保持しているのに対し、Functionコンストラクタで作った関数オブジェクトは常にグローバルなスコープしか保持していません。なぜこの差が生じるかというと、単に仕様でそう決まっているからです。関数に保持されるスコープは関数が作られたときに決定されるので、関数をどこでどのように作ったかによって結果が変わるということです。そして一度作られたあとは変更されることはありません。
この理屈によって、関数式ではクロージャを作ることができるが、Functionコンストラクタでは作ることができない、ということになります。
全くの余談ですが、クロージャが”閉じる”のは、スコープチェーンに組み込まれたActivation Objectがプログラマからは操作不可能だから、という事情によります。よってその他のオブジェクトをスコープチェーンに組み込んでやれば、クロージャと似ているが”開いた”関数を作り出すこともできます(役に立つかどうかは不明ですが)。
var func,
scope = {alert: function(msg){ console.log(msg); }};
//スコープチェーンの中にオブジェクトを組み込む
with(scope) {
func = function(x, y) { alert(x + y); };
}
//グローバルのalertは隠蔽されてconsole.logが実行される
func(1, 2);
//scopeのプロパティを削除することでグローバルなalertが実行されるようになる
delete scope.alert;
func(3, 4);
//再びalertを隠蔽する
scope.alert = function(){};
func(5, 6);
ECMAScript5の用語で言うと、Declarative Environment Recordに関数外部から束縛を追加したり削除したりはできないが、Object Environment Recordならbinding objectを通してそれが可能だということです。
参照
仕様書の中で関連する部分は多岐にわたるため、一部のみを挙げておきます。
- 15.3 Function Objects – Standard ECMA-262 3rd/5th
- 13 Function Definition – Standard ECMA-262 3rd/5th
またこの記事はazuさんのJavaScript Patterns読書記録を踏まえて書かれました。