with なしで変数参照ありの式の文字列を評価する
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ 文字列を eval したい
◆ 変数は eval スコープのローカルじゃなくてオブジェクトのプロパティに入ってる
◆ 変数(プロパティ)だけじゃなくて関数や演算子などの式もある
↓
◆ eval でローカルスコープにもってくる
◆ できれば避けたいやり方
◆ Function 関数で eval して引数形式で渡す
◆ 変数は eval スコープのローカルじゃなくてオブジェクトのプロパティに入ってる
◆ 変数(プロパティ)だけじゃなくて関数や演算子などの式もある
↓
◆ eval でローカルスコープにもってくる
◆ できれば避けたいやり方
◆ Function 関数で eval して引数形式で渡す
ちゃんと書くと文字数がすごく長くなるのでわかりづらいタイトルになっててすみません
今回やりたいのは ユーザ入力の文字列を評価してその値を取得することです
式の文字列を評価するなら JavaScript では eval を使えば一発 なのですが そのままではだめな理由があります
式には
のように普通の JavaScript の式が入ります
eval するスコープで使える変数は オブジェクト形式で渡すので そのまま eval しても参照できません
一番簡単な解決方法は with を使うことです
with は使うべきじゃないと言われていますが 考え無しであちこちで使うようなことをしなければ便利な機能ですし with が最適ってシーンでは避ける必要はないと思います
グローバルスコープの window 自体が with されてるようなものですし JavaScript とは深い関係な機能ですし
……ですが strict モードでは with を使うことはできません
strict モード自体 必須な機能でもなく 使うことのメリット・デメリットがあるので 私は必要なときだけしか strict モードは使いませんし strict 必須にすべきとは思いません
それなのに module では強制 strict となります
余計なことしなくていいのに と思いますが module で使うなら仕方ないのでなんとか with を使わずに実装しないといけません
式じゃなくて変数名と決まってるなら values[name] 形式にできますが 1 + 1 などが来ると機能しなくなります
式をパースして変数名だけを取り出して特別に処理 なんてことは絶対に避けたいです
四則演算くらいのシンプルさならともかく 関数呼び出しや無名関数作成 テンプレートストリング など JavaScript の構文すべてをサポートして変数名だけ取り出すなんてそう簡単にはできません
eslint とか tern とか flow とかで使われてるライブラリを持ってくれば可能かもしれませんがそんな大事にはしたくないです
他に考えられるのが あまり気の進まない方法ですが オブジェクトの中身全部をローカルスコープに持ってくること
動的に var name = value というコードを実行してローカル変数を定義します
パフォーマンスとかわかりやすさとかいろいろな部分でイヤなコードですがシンプルにかける点では一番です
isValidVariableName は省略してますが 変数名として有効化チェックする関数です
正確にチェックするのは結構メンドウなので try-catch で宣言できなければ無視するのでもいいかなと思います
それ以前に関数に渡す引数に変数名に使えないものなんて入れるはずないので 使う側の問題ということでチェックすらなくてもいいかも
キーに変な文字列いれてインジェクション攻撃っぽいこともできなくはないですけど 普通は関数使う人がそんなの渡さないですし そんなことするなら直接 JavaScript で直接書いてしまえばいいだけですからね
const と var を使い分けてるのは for 内で消えてほしい変数と eval のスコープまで残っていてほしい変数を分けるためです
もっと気持ち良いコードで書きたい 何か方法ないの?とあれこれ考えてました
そんなとき ローカルに展開するのは eval よりもっといい方法がある と気づきました
引数に渡せばいいんです
eval 以外にも Function 関数を使って文字列を評価させることができます
「return 」 と書いてる部分だけかっこよくないですが 全体的にはいい感じです
eval を使わずひとつの式でできてるのもいいところです
今回やりたいのは ユーザ入力の文字列を評価してその値を取得することです
式の文字列を評価するなら JavaScript では eval を使えば一発 なのですが そのままではだめな理由があります
式には
- 100
- fn("abc")
- num1 + num2
のように普通の JavaScript の式が入ります
eval するスコープで使える変数は オブジェクト形式で渡すので そのまま eval しても参照できません
一番簡単な解決方法は with を使うことです
function evaluate(expression, values) {
with(values){
return eval(expression)
}
}
evaluate("num + 1", {num: 100})
// 101
with(values){
return eval(expression)
}
}
evaluate("num + 1", {num: 100})
// 101
with は使うべきじゃないと言われていますが 考え無しであちこちで使うようなことをしなければ便利な機能ですし with が最適ってシーンでは避ける必要はないと思います
グローバルスコープの window 自体が with されてるようなものですし JavaScript とは深い関係な機能ですし
……ですが strict モードでは with を使うことはできません
strict モード自体 必須な機能でもなく 使うことのメリット・デメリットがあるので 私は必要なときだけしか strict モードは使いませんし strict 必須にすべきとは思いません
それなのに module では強制 strict となります
余計なことしなくていいのに と思いますが module で使うなら仕方ないのでなんとか with を使わずに実装しないといけません
eval が増える
とは言っても with なしで 変数を参照する場合のみ オブジェクトのプロパティを参照させるなんてできないです式じゃなくて変数名と決まってるなら values[name] 形式にできますが 1 + 1 などが来ると機能しなくなります
式をパースして変数名だけを取り出して特別に処理 なんてことは絶対に避けたいです
四則演算くらいのシンプルさならともかく 関数呼び出しや無名関数作成 テンプレートストリング など JavaScript の構文すべてをサポートして変数名だけ取り出すなんてそう簡単にはできません
eslint とか tern とか flow とかで使われてるライブラリを持ってくれば可能かもしれませんがそんな大事にはしたくないです
他に考えられるのが あまり気の進まない方法ですが オブジェクトの中身全部をローカルスコープに持ってくること
動的に var name = value というコードを実行してローカル変数を定義します
パフォーマンスとかわかりやすさとかいろいろな部分でイヤなコードですがシンプルにかける点では一番です
function evaluate(expression, values) {
for(const key of Object.keys(values)){
isValidVariableName(key) && eval(`var ${key} = values["${key}"]`)
}
return eval(expression)
}
evaluate("num + 1", {num: 100})
// 101
for(const key of Object.keys(values)){
isValidVariableName(key) && eval(`var ${key} = values["${key}"]`)
}
return eval(expression)
}
evaluate("num + 1", {num: 100})
// 101
isValidVariableName は省略してますが 変数名として有効化チェックする関数です
正確にチェックするのは結構メンドウなので try-catch で宣言できなければ無視するのでもいいかなと思います
それ以前に関数に渡す引数に変数名に使えないものなんて入れるはずないので 使う側の問題ということでチェックすらなくてもいいかも
キーに変な文字列いれてインジェクション攻撃っぽいこともできなくはないですけど 普通は関数使う人がそんなの渡さないですし そんなことするなら直接 JavaScript で直接書いてしまえばいいだけですからね
const と var を使い分けてるのは for 内で消えてほしい変数と eval のスコープまで残っていてほしい変数を分けるためです
結構いい感じな解決策
作ってみては見たものの 見直すたびになんかイヤだもっと気持ち良いコードで書きたい 何か方法ないの?とあれこれ考えてました
そんなとき ローカルに展開するのは eval よりもっといい方法がある と気づきました
引数に渡せばいいんです
eval 以外にも Function 関数を使って文字列を評価させることができます
function evaluate(expression, values){
return Function(...Object.keys(values), `return ${expression}`)(...Object.values(values))
}
evaluate("num + 1", {num: 100})
// 101
evaluate("b(a + c)", {a:1, b:function(x){return `{${x}}`}, c:"a"})
// 1a
return Function(...Object.keys(values), `return ${expression}`)(...Object.values(values))
}
evaluate("num + 1", {num: 100})
// 101
evaluate("b(a + c)", {a:1, b:function(x){return `{${x}}`}, c:"a"})
// 1a
「return 」 と書いてる部分だけかっこよくないですが 全体的にはいい感じです
eval を使わずひとつの式でできてるのもいいところです