ハードウェアからサーバ・アプリまでワンストップで開発

Javascriptのeval(1)

ogochan

この前の情報共有システムのエントリの最後に「evalは落とし穴があるよ」という話を書いたので、ここで続き。

まぁ、eval()自体そんなに使って良いものじゃないんで、用のある人は少ないとは思いますけど。

eval()の危険性については常識でもあるしあまり興味がないので割愛します。「evalじゃなくて'Function'使えよ」って話は、そもそもeval()の解説のページに書いてあるんで、その辺気になる人はそっちで。

件のシステムでは、プログラムが入力できるフィールドは任意個持てます。まぁ、これはJupyter notebookを見れば当然ですね。

では、

このようにした時、下のフィールドを実行させると何が起きるでしょう?

このハードコピーを簡単にコード化すると、

eval('let a = 10');
eval('console.log(a)');

となります。

これを実行すると、後の方のevalはエラーとなります。曰く「aは宣言されてない」というエラーです。

letはブロックの中だけで通用する変数の宣言です。そして、eval()はどうも「入力された文字列をブロックとして評価する」もののようです。

となると、最初のeval()の中で宣言したaと、後のeval()の中で使っているaとは別物になってしまいます。そして、後のeval()の中ではaは宣言されていませんから、「aは宣言されていない」というエラーになるのは当然ですね。

ではこのletの代わりにvarを使うとどうなるでしょう?

実はvarで宣言した場合はエラーになりません。そして、コンソールには'10'と出力されます。

varはそれを書いた場所に関係なく、グローバルなスコープを持つため、eval()の中のブロックの外に存在することになるわけです。

そんなわけで、単純にREPLをeval()を使って書くと、こんな問題を持つことになります。

これが他の言語のevalだと、「環境」というオブジェクトがあってそれを持ち回ると「異なるevalでも同じブロックとする」ことが可能だったりするのですが、ブラウザのJavascriptの場合は簡単には解決がつきません。Node.jsはvmというライブラリをごにょごにょすると、「環境の持ち歩けるeval」を作ることが出来るのですが(safe-evalというライブラリもある)、ブラウザではそれは出来ません。

ところがStaboardだとこれは解決していたりします。なので、Starboardでは「異なるセルに書いたlet」も有効に使えます。もっとも、Starboardはこの辺を解決するために、元のコードをbabelを使って変形していたりするので、なんとゆーか力技という感じです。

他方、個々のセルがブロックとなるというのは、見方によっては自然とも言えます。下手に副作用とか起きませんし、letは同じものを複数書くとエラーになりますから、どっちが良いんだという話をすると、

どっちもどっち

という感じになります。

「選択肢の提供」という意味だと、「letはセルの中のスコープ、どうしてもグローバルなスコープにしたければvar使え」という使い分けが可能になるので、ここで頑張ってletのスコープをどうこうするよりは、そのままでも良いかなと思っています。グローバルなスコープの変数がホイホイ定義できるのは、それはそれで厄介ですし。

ちなみに、同じ理由でconstもダメです。constが使えないのは痛いですが、これもまぁしょうがないかなと。

constletはダメですが、名前付きのfunctionは問題ありません。つまり

eval('function aa() { return 100}');
eval('console.log(aa())');

これであれば、結果は'100'と表示されます。

とか書くと、なんかモダンなJavascript全般がダメって感じになってちょっと残念ではありますが、とりあえず現段階ではこれで良いことにしておきます。どうしても我慢が出来なかったら対策を考えたいと思います。