質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

Q&A

解決済

5回答

7626閲覧

eval

aaaaaaaa

総合スコア501

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

0グッド

3クリップ

投稿2016/09/12 11:14

javascriptのeval関数は、引数として与えられた文字列をjavascriptのソースとして扱うものです。
最後に評価された値を戻り値にする、必要以上に使わない、与えた引数が文字列でないと、与えられたものをそのまま返す、
文字列にしたかったら与えられた引数を文字列型にするString.prototype.toStringメソッドを使い文字列型に変換する、などと解説サイトに記述されていました。そのほか、javascriptのリテラル表記の一部?でファイルの書き方の規則の一つであるJSONともかかわりが深いそうです。

ここで質問ですが、eval関数は、どのようなときに利用するのが適宜なのでしょうか。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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

JSONeval で関わるがあるとは思いませんが、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
think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aaaaaaaa

2016/09/13 11:04

ご回答有難うございます。とても詳細に返答してくださって助かります。 初めて見たのですが、eval()の式である(0,eval)()は、どのいちにいてもグローバルスコープをもつことができることが解かりました。ここで一つ疑問なのですが、「eval() はローカルスコープで実行される」の部分は、「2,1,2,ReferenceError: b is not defined」となっていますが、再現してみると「1,1,2,ReferenceError: b is not defined」となってしまいました。これは、(0,eval)()でグローバル変数を関数内で宣言しただけで ローカル変数は、宣言したときと変わらない「1」であるからこうなったのだと認識したのですが、これは誤植でしょうか。誤解でしたらすみません。
think49

2016/09/13 11:23

変数aの挙動はその認識で正しいと思いますが、「ReferenceError: b is not defined」となる理由は Function() が関数スコープで実行され、グローバル変数が宣言されないからです、
guest

0

現代のJavasScript環境で、evalを使うべき場面が1つあって、「環境によらずグローバルオブジェクトを取得したい場合」です。

この場合は、(0, eval)('this')のように書きます。

JavaScriptでは、角括弧を使った記法で、グローバル変数を名前から参照することもできますので、多少凝ったことをする場合にもほぼevalの出番はありません。

投稿2016/09/12 22:25

maisumakun

総合スコア145121

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

evalを使ったらオブジェクト志向もなにもなくなります
絶対に使わないという強い意志を持って取り組むのが肝要です

投稿2016/09/12 11:59

yambejp

総合スコア114572

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

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
gaogao_9

総合スコア103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2016/09/13 18:44

> しかし、この方法では、"JS"や"JavaScript"などのフィルタリングワードを、予め文字リテラルとして埋め込む必要があり、ユーザからの入力などから、動的に受け取る事ができません。 wordList を for 文で回したり、new RegExp で正規表現オブジェクトを生成すれば可能です。 実際に試したところでは new Function が最遅でした。基本的に関数コストが高く、new Function の文字列をパースするコストも高いと思われます。 https://jsfiddle.net/hyrptoda/ arrayFilterByWhileWhile: 93.593ms arrayFilterByWhileRegExp: 1721.070ms arrayFilterByFilter: 1822.533ms arrayFilterByFunctionMap: 10575.095ms arrayFilterByFunctionWhile: 11618.206ms
think49

2016/09/13 19:30 編集

WordListをキャッシュして Function() を呼び出す回数を1回に抑えたら大分高速化されました。 https://jsfiddle.net/hyrptoda/1/ createArrayFilterByWhileWhile: 99.595ms createArrayFilterByWhileRegExp: 1644.619ms createArrayFilterByFilter: 1705.941ms createArrayFilterByFunctionMap: 95.498ms createArrayFilterByFunctionWhile: 91.704ms 同じWordListで1回しか呼び出さなければ WhileWhile、複数回呼び出すなら FunctionWhile に軍配が上がりそうです。 が、WhileWhile と FunctionWhile の差は誤差の範囲といえなくもないですね…。
gaogao_9

2016/09/13 22:39

think49 さん 検証ありがとうございます。私の方でも以前に検証したことがあるのですが、当時と同じような結果となったので安心しています。 本件は生成コストを支払って、以降複数回呼び出し時のコストを1msでも速くすることが主題であります。エゴサの速さはふぁぼの速さです。Twitterでは命です。 また、Tweetを解析して結果を表示するサイトのようなデータ解析の分野でも、検索API等を用いてビッグデータを処理することになるので、速いことに越したことはないでしょう。 よって、以降はWordListをキャッシュして、Functionを1回だけ呼び出す場合についてのみ話します。 そして、話を複雑にしないためにあえて今回は「いずれかを含む」OR検索のお話をしました。 実用上はこれを「ユーザが任意に決定できる」OR検索、もしくはAND検索可能のいずれもが可能なように、入れ子のオブジェクトツリー構造でWordListを定義して処理することで自由度が高まります。 その場合には、if文によるOR検索かAND検索かの判定部分を、WhileWhileでは事前処理することが出来ず、実行時にどちらであるかを読み取って、そこから処理する必要があります。 (と考えています。もしこの部分で他の方法があれば教えて下さい。) 一方で、FunctionWhileによる方法ですと、条件分岐における論理演算部分も、FunctionWhileではメタプログラミングで決定することが出来るため、更に差が出るはずです。 (この辺りも以前データを取りましたが、WordList及びTweetListを現実的な数字設定(WordList << TweetList)した場合に、100ms~1s単位で結果に差が出てきました。)
think49

2016/09/14 03:00

To: gaogao_9 さん > その場合には、if文によるOR検索かAND検索かの判定部分を、WhileWhileでは事前処理することが出来ず、実行時にどちらであるかを読み取って、そこから処理する必要があります。 関数生成時にAND/OR条件を判定して関数式を返すようにすれば可能だと思います。 # 一昔前の addEvent と同じ原理ですね(下記URLはわかりやすいように一番古いリビジョンのURLです)。 # https://gist.github.com/think49/758906/db5cd03c967c368a50d095854850fb16cf9f95b3 https://jsfiddle.net/hyrptoda/2/ createArrayFilterByWhileWhile(OR): 105.182ms createArrayFilterByWhileRegExp(OR): 1649.554ms createArrayFilterByFilter(OR): 1720.193ms createArrayFilterByFunctionMap(OR): 99.355ms createArrayFilterByFunctionWhile(OR): 97.765ms createArrayFilterByWhileWhile(AND): 64.833ms createArrayFilterByWhileRegExp(AND): 1996.554ms createArrayFilterByFilter(AND): 2119.080ms createArrayFilterByFunctionMap(AND): 69.251ms createArrayFilterByFunctionWhile(AND): 66.157ms 総合的にはWhileWhileとFunctionWhileは「ほぼ同等」という結果になりました。 最も、TweetList, WordList の数によって結果は変わると思いますが…。
gaogao_9

2016/09/14 14:49

think49 さん うーむ、上手く伝わってなかった、失礼。 言いたかった実装は、任意の論理式を構築できる、というものです。 例えば、「変数sは、JavaScriptタグを含む、もしくはJSをタグを含み、かつHTML/CSSのいずれかの単語を含むか、幼女という単語を含まない」みたいなのを、条件文で書くと s.includes("JavaScript") || (s.includes("JS") && (s.includes("HTML") || s.includes("CSS") || !s.includes("幼女"))) になります。 見ての通り、論理演算を用いた場合には、1行で書くことが出来るため、if文1回で済むのですが、もしこれをWhileWhileで実現するなると、どうしても実行時に判定が必要になるのでは?という主張でした。 addEventの例のように、関数式を返す、という方法は想定外でした。 しかし、この方法でも、実行時に論理の段数分だけ関数を呼び出してしまうため、関数呼び出しコストが大きくなってしまいます。 いずれにしても実行時間に悪影響を与える要因になりますよね。 この辺りは、ベンチに使うTweetList/WordListで結構数字が変わってくることが予想されますので、計測コードもあまり意味を成さないと思います。 (ただ、速度が逆転することはないはずです。ベンチに使うパラメータによっては誤差程度の実行速度にもなりますが、理論上の性能はWhileWhile≦FunctionWhileのはず。反例が思いつきそうであれば教えて頂けると幸いです。) そういうわけで、速度最優先の案件で検証困難な部分は、考えうる手のうち最速を採用したほうが良いので、そういう場合はFunctionを用いるのは有用だよ。ってことでお茶を濁したいです。
guest

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

LLman

総合スコア5592

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aaaaaaaa

2016/09/14 11:14 編集

ご回答有難うございます。 >>それらに相当する別のドメインのDSLを使えたら便利だ、 別の領域の特化言語を使う、つまり、X言語のなかでY言語を使うという意味ですか。 そしてそれを実現するためにevalがあるということなのでしょうか。
LLman

2016/09/14 11:23

>「X言語のなかでY言語を使う」 そうです。たとえば色々な言語で使える「正規表現」がそのY言語の例ですね。 >「それを実現するためにevalがある」 そうです。とくに内部DSLにevalを使います。 外部DSLではパーサを書きますが、内部DSLの方が開発コストが低いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問