今回も改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用までで知ったことから。
昔も似たようなタイトルで投稿したことあるのですが(JavaScriptでローカル変数と同じ名前のグローバル変数を呼び出す方法 | while(isプログラマ))、これはフロントエンド(ウェブブラウザ)限定の方法。グローバル変数はWindowオブジェクトに属しているから、windowオブジェクトのプロパティとして呼び出せばいいという話でした。今回は環境を問わず、例えばnode.jsでも通じる話。
その前に、JavaScriptでの関数の指定方法は4つあります。
1つ目は下記のようにfunction命令で定義する方法。
function func(arg){ console.log(arg); return 1; }
2つ目は下記のようにFunctionオブジェクトのコンストラクターを経由して定義する方法。
var func = new Function('arg', 'console.log(arg);return 1;');
3つ目は下記のように関数リテラルで定義する方法
var func = function(arg){ console.log(arg); return 1; }
4つ目はES2015で追加されたアロー関数で定義する方法
var func = (arg) => { console.log(arg); return 1; };
どれも、『func(123)』とするとコンソールに『123』と出力されますが、実はそれぞれ微妙に違うそうです。例えば、function命令は静的な構造で定義より上で呼び出す記述があっても実行されるのに対し、関数リテラルやFunctionコンストラクターは実行時に評価されるので、定義より上で呼び出す記述があればエラーとなる等。
で、今回その違いで初めて知ったのが、Functionコンストラクターで定義した関数内の変数は、定義したスコープ内に同じ変数名があったとしてもグローバル変数を参照するということ。Functionコンストラクターなんて普段使わないから知りませんでした。
というわけで、以下サンプル。
// グローバル関数 func = function(val){ console.log("global:" + val); } // グローバル変数 apple = 10; func(apple); //=> "global:10" // 即時関数1 ;(function(){ var apple = 20; var func = function(val){ console.log("local:" + val); }; func(apple); //=> "local:20" })(); // 即時関数2 ;(function(){ var apple = 20; var func = function(val){ console.log("local:" + val); }; var globalFunc = (new Function('return func'))(); var globalApple = (new Function('return apple'))(); globalFunc(globalApple); //=> "global:10" })();
2つ目の即時関数で、globalFuncやglobalAppleを用意して、それぞれFunctionコンストラクターで定義した即時関数からfunc関数とapple変数を取得しています。この結果は、同じスコープ内にappleという変数とfuncという関数があるにも関わらず、『global:10』となりました。つまり、グローバル変数を取得しているわけです。
まあ、現在のスコープ内に定義されている変数名と同じ名前のグローバル変数を取得することなんてそうそうないとは思いますが、Functionコンストラクターで定義した関数の中の変数はグローバル変数を参照するということは覚えておいたほうがいいかもしれません。「スコープ内で同じ名前の変数が存在してるのに、何でエラーになるんだろう?」となりかねません(もちろん、エラーになる理由はグローバル変数に同名の変数がないから)。
まあでも、最初に紹介した本にも書いてありましたが、Functionコンストラクターでの関数宣言は利用しないほうがいいと思います。分かりづらいです。文字列変数を利用して生成できるというメリットはあるかもしれませんが、その変数が外部から参照している値を利用していたらセキュリティ的に問題になる可能性があります。
コメント
即時関数とは言いません。無名関数の呼び出し、function式の呼び出し、ラムダ式の呼び出しと言います。
27行目以降をfunction式で書いてみてください。
すぐ上のスコープを参照するはずです。
意味的に同じはずなのに、まったく違う振る舞いをする、設計ミスですねこれは。