javascriptのeval関数は、引数として与えられた文字列をjavascriptのソースとして扱うものです。
最後に評価された値を戻り値にする、必要以上に使わない、与えた引数が文字列でないと、与えられたものをそのまま返す、
文字列にしたかったら与えられた引数を文字列型にするString.prototype.toString
メソッドを使い文字列型に変換する、などと解説サイトに記述されていました。そのほか、javascriptのリテラル表記の一部?でファイルの書き方の規則の一つであるJSONともかかわりが深いそうです。
ここで質問ですが、eval関数は、どのようなときに利用するのが適宜なのでしょうか。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
ベストアンサー
eval is evil
"eval is evil (eval は悪)" とよくいわれますが、これは「eval
が何でもできる為、扱いが非常に難しく、意図せず危険なコードを実行できてしまう」性質にあります。
例えば、eval の間違った使い方として次のコードがあります。
HTML
1<input id="hoge" type="text" value="1"> 2<script> 3var value = document.getElementById('hoge').value; 4value = eval(value); // eval() を使って Number 型に変換する 5console.log(eval(value)); // 1 6</script>
このコードは期待通りに動作しますが、テキストボックスに alert("foo");
と書かれたらアラートが現れるように任意のコードを実行できてしまう問題があります。
eval
を安全に使う為には入力される文字列を厳しくチェックして制限する必要があります。
この場合は数値文字列であれば良いので次のように書き換えてみましょう。
JavaScript
1var value = document.getElementById('hoge').value; 2value = /^\d+$/test(value) ? eval(value) : NaN; // 数値文字列であれば eval() で Number 型に変換し、数値文字列でなければ NaN を返す 3console.log(eval(value)); // 1
うまくいきました。
が、整数はパスしますが、小数や負の数に対応していないようですので対応してみましょう。
JavaScript
1var value = document.getElementById('hoge').value; 2value = /^-?\d+(\.\d+)?$/test(value) ? eval(value) : NaN; // 数値文字列であれば eval() で Number 型に変換し、数値文字列でなければ NaN を返す 3console.log(eval(value)); // 1
これで完璧…と思いきや、「∞
に対応していない」との指摘が挙がった為、更に修正してみましょう。
JavaScript
1var value = document.getElementById('hoge').value; 2value = /^-?(?:\d+(\.\d+)?|∞)$/test(value) ? eval(value) : NaN; // 数値文字列であれば eval() で Number 型に変換し、数値文字列でなければ NaN を返す 3console.log(eval(value)); // 1
さて、これで一応は完成しました。
この手の議論で「eval
はユーザ入力文字列ではなく、プログラマが指定した文字列を入力する場合に限定して安全だ」と主張する方をたまに見かけますが、このようにプログラマが想定した文字列が不十分である為に想定外のバグを生み出してしまう場合があります。
プログラマが完璧に全てのパターンを想定してコードを書けば問題ないのですが、人間だれしも間違いを起こすものです。
というわけで、この場合の最適解は Number()
を使う事です。
JavaScript
1var value = document.getElementById('hoge').value; 2value = Number(value); // Number() で Number 型に変換する (Number 型に変換不可能なら NaN を返す) 3console.log(eval(value)); // 1
とてもシンプルなコードになりました。
eval
は何でもできる反面、安全に使う為に入力文字列を完璧に制限して特定の機能特化型に書き換えなければなりません。
それならば、初めから機能特化したビルトイン関数を使う方が安全といえます。
Function()
eval()
によく似た関数として Function()
があります。
Function() は eval() と違い、常にグローバルコードと同等に評価する為、グローバルオブジェクトを得る手段としてよく使われます。
JavaScript
1'use strict'; 2(function () { 3 console.log(Function('return this')()); // Window (グローバルオブジェクト) 4 console.log(eval('this')); // undefined 5 console.log((0,eval)('this')); // Window (グローバルオブジェクト) 6}());
3番目のコード (0,eval)('this')
もグローバルスコープで実行している為、グローバルオブジェクトを参照できます。
これは ES5 から拡張された機能で eval
が式として評価された場合にグローバルスコープで実行するようにスイッチしています。
ES5 からの拡張の為、ES5 を実装していない IE8 では (0,eval)('this')
は期待通りに動作しません。
後方互換性の観点から Function('return this')()
は有用といえます。
eval() はローカルスコープで実行される
eval()
はローカル変数を破壊できますが、Function()
は破壊しません。
JavaScript
1(function () { 2 var a = 1, b = 1; 3 (0,eval)('var a = 2'); 4 Function('var b = 2')(); 5 console.log(a); // 2 6 console.log(b); // 1 7}()); 8console.log(a); // 2 9console.log(b); // ReferenceError: b is not defined
eval()
を式評価するとグローバルコードとして実行される為、ローカル変数を破壊しませんが、グローバル変数を定義して汚染します。
Function
はグローバルコードと同等に評価されるものの関数スコープで実行される為、グローバル変数を汚染しません。
JavaScript
1(function () { 2 var a = 1, b = 1; 3 (0,eval)('var a = 2'); 4 Function('var b = 2')(); 5 console.log(a); // 2 6 console.log(b); // 1 7}()); 8console.log(a); // 2 9console.log(b); // ReferenceError: b is not defined
Strict Mode で実行した場合、eval()
はローカル変数の書き換えを防止するように動作が書き換えられますが、グローバル変数の書き換えは許可されます。
HTML
1<input id="hoge" type="text" value="1"> 2<script> 3'use strict'; 4(function () { 5 var a = 1, b = 1; 6 eval('var a = 2'); 7 Function('var b = 2')(); 8 console.log(a); // 2 9 console.log(b); // 1 10}()); 11console.log(a); // ReferenceError: a is not defined 12console.log(b); // ReferenceError: b is not defined 13</script> 14<script> 15'use strict'; 16(function () { 17 var a = 1, b = 1; 18 (0,eval)('var a = 2'); 19 Function('var b = 2')(); 20 console.log(a); // 2 21 console.log(b); // 1 22}()); 23console.log(a); // 2 24console.log(b); // ReferenceError: b is not defined 25</script>
従って、グローバル変数の書き換えを防止する意味でも (0,eval)()
と比較して Function()
は有用といえます。
JSON
JSON
と eval
で関わるがあるとは思いませんが、JSONのPolyfillコードとして eval はよく見かけました。
上述の理由で Function()
の方が安全ですし、ただの文字列フォーマットに過ぎないので eval()
も Function()
もいらないのではないか、とも思います。
この場合、オブジェクト初期化子を使ってオブジェクトを生成し、パーサでパースした key
, value
を代入するだけで済みます。
JavaScript
1var obj = {}; 2while (token = pattern.exec(JSONString)) { 3 // token を評価して key, value を求める 4 obj[key] = value; 5}
eval
式(or Function
式)のコードでは正規表現チェック後に eval(JSONString)
をするだけで済むので楽に書けますが、安全性の面から良くないと考えています。
JSONはJavaScript文法上の「オブジェクト初期化子」「配列初期化子」と同じ性質を持つ文字列フォーマットにすぎません。
(しかも、実際のところは関数を評価できず、Object 型は new Object
だけ、という機能限定版です。)
私は「JSON と eval に関連性は全くない」と考えます。
Re: aaaaaaaa さん
投稿2016/09/13 01:38
編集2016/09/13 11:20総合スコア18189
0
現代のJavasScript環境で、eval
を使うべき場面が1つあって、「環境によらずグローバルオブジェクトを取得したい場合」です。
この場合は、(0, eval)('this')
のように書きます。
JavaScriptでは、角括弧を使った記法で、グローバル変数を名前から参照することもできますので、多少凝ったことをする場合にもほぼeval
の出番はありません。
投稿2016/09/12 22:25
総合スコア145971
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
他の方がeval
の危険性や、Functionコンストラクタ
との比較についてよく書かれているので、私からは実用的なFunctionコンストラクタ
の使い方に対して1つ示します。
以下に記述する内容は、
「理論上はeval
でも同様のことが実現できることに対して、実用上はFunction
を用いる。」
といったものですので、この質問とも関連していると思います。
さて本題ですが、Function
を上手に利用すると、高速化が見込める場合があります。
他の人が触れた話の中では、 LLman さんが書いた メタプログラミング が一番近い話になります。
eval
の持つ動的な評価という性質は、色々な場面で応用が効きます。
その中でも、一番わかり易いのはfilter
処理でしょう。
例えば、複数個与えられる文章に対して、特定のワードが含まれている文章を抽出する。
という処理について考えてみましょう。
aaaaaaaa さんがピンとくるか不安ですが、Twitterで言えばエゴサーチ機能にあたる部分の実装です。
(因みに本来のエゴサーチの意味は、自分自身に関することの検索なので、この表現は誤用なのですが、他に適した表現方法が思いつかなかったため、ご容赦ください。)
例えば、次のようなゴミみたいなつぶやきの中から、「JS」もしくは「JavaScript」という単語が含まれるつぶやきだけを抽出して、JSに関する話題だけを取ってくるとします。
const tweetList = [ "起きた", "はいふりがすき", "そういえばteratailでJSに関する質問ないか見てこよう", "JavaScript難しい…。", "vimとかemacsとかで争うのはやめろよ。最強は秀丸って結論出てるんだしさ。", ]; const wordList = [ "JS", "JavaScript", ];
これに対して、普通にfilter関数を用いてシンプルに実装すると、以下のようになりますよね。
function createFilter(wordList){ return function wordFilter(tweetList){ return [...new Set([ ...wordList.reduce((list, word)=> list.concat( tweetList.filter((tweet)=> tweet.includes(word)) ), []) ])]; } } const tweetFilter = createFilter(wordList); console.log(tweetFilter(tweetList)); // 出力結果: // [ // "そういえばteratailでJSに関する質問ないか見てこよう", // "JavaScript難しい…。" // ]
しかし、大量のツイートに対して、Array#filter
で毎回毎回処理するのは、お察しのとおり低速です。
このフィルタが、もしも以下の様なコードで書かれていたら、フィルタ部分を単純なif文でのみ処理できるため、高速に動作することが考えられますよね。
function tweetFilter(tweetList){ const outputList = []; for(const tweet of tweetList){ if(!tweet.includes("JS") && !tweet.includes("JavaScript")) continue; outputList.push(tweet); } return outputList; }
しかし、この方法では、"JS"
や"JavaScript"
などのフィルタリングワードを、予め文字リテラルとして埋め込む必要があり、ユーザからの入力などから、動的に受け取る事ができません。
そこでFunction
の出番です。
wordList
を引数に受け取り、上記のようにif文と論理演算だけで構成されたフィルタの関数を動的に定義するメタプログラミングを行います。
function createFilter(wordList){ const expressionBody = wordList.map((word)=> `!tweet.includes("${word}")`).join(" && "); return new Function("tweetList", ` const outputList = []; for(const tweet of tweetList){ if(${expressionBody}) continue; outputList.push(tweet); } return outputList; `); } const tweetFilter = createFilter(wordList); console.log(tweetFilter(tweetList)); console.log(tweetFilter.toString()); // toStringの出力は環境によって変わるので参考程度で。 // 出力結果: // [ // "そういえばteratailでJSに関する質問ないか見てこよう", // "JavaScript難しい…。" // ] // // function anonymous(tweetList) { // // const outputList = []; // // for(const tweet of tweetList){ // if(!tweet.includes("JS") && !tweet.includes("JavaScript")) continue; // // outputList.push(tweet); // } // // return outputList; // // }
このように、メンテナビリティを代償として、部分部分での最適化チューニングのためにFunction
(もしくはeval
)を用いることが出来ます。
グローバルオブジェクトを取ってくる、というものと比べたら、こちらのほうが本懐と言いますか、本来の使い方かと思われます。
一つ注意すべきことですが、ユーザからの入力で動的生成する場合に関しては、スクリプトに直接ユーザが介入できるため、インジェクション攻撃の対象になる虞があります。
そのため、入力に対するバリデーションは十分に行う必要があり、この辺りも、暴れ馬と言いますか、扱いの難しさではあります。
(バリデーションの扱いの難しさについては think49 さんが詳しく触れていますね。)
このように非常に上級者向けで、使い方が難しいですが、使えそうな場面ではぜひ利用してみてください。
投稿2016/09/13 16:19
編集2016/09/13 17:57総合スコア103
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/09/13 19:30 編集
2016/09/13 22:39
2016/09/14 03:00
2016/09/14 14:49
0
「eval」とは、データをプログラムに変換する方法です。
javaScriptに限らず、Rubyなど他言語にもあります。
メリットとしては、メタプログラミングができること。
デメリットとしては、セキュリティ上の問題があること。
JavaScriptはブラウザのスクリプト出身でWeb特化なので、
Webではセキュリティが重要になるので(PHPなどと並んで)、
evalのデメリットが強調されるのも仕様がないかもしれません。
さて、evalを使い始めたのはメタプログラミング王国のLispです。
その流れを汲むSmalltalkやRubyでも「DSL」を使う文化があり、
そういうメタプログラミングの文脈でevalが使われます。
――「メタプログラミング」を連呼しても、イメージしにくいかもしれません。
しかし、ご質問に挙げられたJSONに限らず、HTMLやXMLもDSLです。
とすると、それらに相当する別のドメインのDSLを使えたら便利だ、
ということを想像すれば、比較的イメージしやすいかと思います。
投稿2016/09/12 12:16
総合スコア5592
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/09/14 11:14 編集
2016/09/14 11:23
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/09/13 11:04
2016/09/13 11:23