eval
を使用せずにJavaScriptの文字列に格納された数式を計算する方法はありますか?
通常、私は次のようなことをします
var apa = "12/5*9+9.4*2";
alert(eval(apa));
だから、誰もeval
の代替案を知っていますか?
これはまさにevalを使用すべき場所です。そうしないと、文字列をループして数値を生成する必要があります。そのためにはisNaNメソッドを使用する必要があります。
Mhh、Function
- constructorを使用できます:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
function evil(fn) {
return new Function('return ' + fn)();
}
console.log( evil('12/5*9+9.4*2') ); // => 40.4
特にこのようなケースでは、evalに問題はありません。安全のために、まず正規表現で文字列をサニタイズできます:
// strip anything other than digits, (), -+/* and .
var str = "12/5*9+9.4*2".replace(/[^-()\d/*+.]/g, '');
alert(eval(str));
Evalは、このような条件のために構築されました。
別のメソッドが必要な場合は、evalが実行しようとしていることの正確なJavascript実装を使用する必要があります。
これは、(updated(2011 -06-26):入力ボックス付きクリーナー)。
http://jsfiddle.net/vol7ron/6cdfA/
注:
使用するために編集(2017-05-26)SO Snippet:
function calculate(input) {
var f = {
add: '+',
sub: '-',
div: '/',
mlt: '*',
mod: '%',
exp: '^'
};
// Create array for Order of Operation and precedence
f.ooo = [
[
[f.mlt],
[f.div],
[f.mod],
[f.exp]
],
[
[f.add],
[f.sub]
]
];
input = input.replace(/[^0-9%^*\/()\-+.]/g, ''); // clean up unnecessary characters
var output;
for (var i = 0, n = f.ooo.length; i < n; i++) {
// Regular Expression to look for operators between floating numbers or integers
var re = new RegExp('(\\d+\\.?\\d*)([\\' + f.ooo[i].join('\\') + '])(\\d+\\.?\\d*)');
re.lastIndex = 0; // take precautions and reset re starting pos
// Loop while there is still calculation for level of precedence
while (re.test(input)) {
output = _calculate(RegExp.$1, RegExp.$2, RegExp.$3);
if (isNaN(output) || !isFinite(output))
return output; // exit early if not a number
input = input.replace(re, output);
}
}
return output;
function _calculate(a, op, b) {
a = a * 1;
b = b * 1;
switch (op) {
case f.add:
return a + b;
break;
case f.sub:
return a - b;
break;
case f.div:
return a / b;
break;
case f.mlt:
return a * b;
break;
case f.mod:
return a % b;
break;
case f.exp:
return Math.pow(a, b);
break;
default:
null;
}
}
}
label {
display: inline-block;
width: 4em;
}
<div>
<label for="input">Equation: </label>
<input type="text" id="input" value="12/5*9+9.4*2-1" />
<input type="button"
value="calculate"
onclick="getElementById('result').value = calculate(getElementById('input').value)" />
</div>
<div>
<label for="result">Result: </label>
<input type="text" id="result" />
</div>
Shunting-yardアルゴリズム の実装は、単項プレフィックス(例:-
)および後置(例:!
)演算子、および関数(例:sqrt()
)表記の追加サポート付きです。 Calculation.defineOperator
メソッドを使用すると、より多くの演算子/関数を簡単に定義できます。
"use strict";
class Calculation {
constructor() {
this._symbols = {};
this.defineOperator("!", this.factorial, "postfix", 6);
this.defineOperator("^", Math.pow, "infix", 5, true);
this.defineOperator("*", this.multiplication, "infix", 4);
this.defineOperator("/", this.division, "infix", 4);
this.defineOperator("+", this.last, "prefix", 3);
this.defineOperator("-", this.negation, "prefix", 3);
this.defineOperator("+", this.addition, "infix", 2);
this.defineOperator("-", this.subtraction, "infix", 2);
this.defineOperator(",", Array.of, "infix", 1);
this.defineOperator("(", this.last, "prefix");
this.defineOperator(")", null, "postfix");
this.defineOperator("min", Math.min);
this.defineOperator("sqrt", Math.sqrt);
}
// Method allowing to extend an instance with more operators and functions:
defineOperator(symbol, f, notation = "func", precedence = 0, rightToLeft = false) {
// Store operators keyed by their symbol/name. Some symbols may represent
// different usages: e.g. "-" can be unary or binary, so they are also
// keyed by their notation (prefix, infix, postfix, func):
if (notation === "func") precedence = 0;
this._symbols[symbol] = Object.assign({}, this._symbols[symbol], {
[notation]: {
symbol, f, notation, precedence, rightToLeft,
argCount: 1 + (notation === "infix")
},
symbol,
regSymbol: symbol.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
+ (/\w$/.test(symbol) ? "\\b" : "") // add a break if it's a name
});
}
last(...a) { return a[a.length-1] }
negation(a) { return -a }
addition(a, b) { return a + b }
subtraction(a, b) { return a - b }
multiplication(a, b) { return a * b }
division(a, b) { return a / b }
factorial(a) {
if (a%1 || !(+a>=0)) return NaN
if (a > 170) return Infinity;
let b = 1;
while (a > 1) b *= a--;
return b;
}
calculate(expression) {
let match;
const values = [],
operators = [this._symbols["("].prefix],
exec = _ => {
let op = operators.pop();
values.Push(op.f(...[].concat(...values.splice(-op.argCount))));
return op.precedence;
},
error = msg => {
let notation = match ? match.index : expression.length;
return `${msg} at ${notation}:\n${expression}\n${' '.repeat(notation)}^`;
},
pattern = new RegExp(
// Pattern for numbers
"\\d+(?:\\.\\d+)?|"
// ...and patterns for individual operators/function names
+ Object.values(this._symbols)
// longer symbols should be listed first
.sort( (a, b) => b.symbol.length - a.symbol.length )
.map( val => val.regSymbol ).join('|')
+ "|(\\S)", "g"
);
let afterValue = false;
pattern.lastIndex = 0; // Reset regular expression object
do {
match = pattern.exec(expression);
const [token, bad] = match || [")", undefined],
notNumber = this._symbols[token],
notNewValue = notNumber && !notNumber.prefix && !notNumber.func,
notAfterValue = !notNumber || !notNumber.postfix && !notNumber.infix;
// Check for syntax errors:
if (bad || (afterValue ? notAfterValue : notNewValue)) return error("Syntax error");
if (afterValue) {
// We either have an infix or postfix operator (they should be mutually exclusive)
const curr = notNumber.postfix || notNumber.infix;
do {
const prev = operators[operators.length-1];
if (((curr.precedence - prev.precedence) || prev.rightToLeft) > 0) break;
// Apply previous operator, since it has precedence over current one
} while (exec()); // Exit loop after executing an opening parenthesis or function
afterValue = curr.notation === "postfix";
if (curr.symbol !== ")") {
operators.Push(curr);
// Postfix always has precedence over any operator that follows after it
if (afterValue) exec();
}
} else if (notNumber) { // prefix operator or function
operators.Push(notNumber.prefix || notNumber.func);
if (notNumber.func) { // Require an opening parenthesis
match = pattern.exec(expression);
if (!match || match[0] !== "(") return error("Function needs parentheses")
}
} else { // number
values.Push(+token);
afterValue = true;
}
} while (match && operators.length);
return operators.length ? error("Missing closing parenthesis")
: match ? error("Too many closing parentheses")
: values.pop() // All done!
}
}
Calculation = new Calculation(); // Create a singleton
// I/O handling
function perform() {
const expr = document.getElementById('expr').value,
result = Calculation.calculate(expr);
document.getElementById('out').textContent = isNaN(result) ? result : '=' + result;
}
document.getElementById('expr').addEventListener('input', perform);
perform();
// Tests
const tests = [
{ expr: '1+2', expected: 3 },
{ expr: '1+2*3', expected: 7 },
{ expr: '1+2*3^2', expected: 19 },
{ expr: '1+2*2^3^2', expected: 1025 },
{ expr: '-3!', expected: -6 },
{ expr: '12---11+1-3', expected: -1 },
{ expr: 'min(2,1,3)', expected: 1 },
{ expr: '(2,1,3)', expected: 3 },
{ expr: '4-min(sqrt(2+2*7),9,5)', expected: 0 },
{ expr: '2,3,10', expected: 10 }
]
for (let {expr, expected} of tests) {
let result = Calculation.calculate(expr);
console.assert(result === expected, `${expr} should be ${expected}, but gives ${result}`);
}
#expr { width: 100%; font-family: monospace }
Expression: <input id="expr" value="min(-1,0)+((sqrt(16)+(-4+7)!*---4)/2)^2^3"><p>
<pre id="out"></pre>
Evalを使用したくない場合は、既存の式エバリュエーターライブラリを使用する必要があります。
http://silentmatt.com/javascript-expression-evaluator/
http://www.codeproject.com/KB/scripting/jsexpressioneval.aspx
独自のロールもできます:)
eval()
を使用せずにすべての算術ルールを実装するのに数時間を費やし、最後にnpm string-math でパッケージを公開しました。すべてが説明にあります。楽しい
eval
と同等の構文を探している場合は、new Function
。スコーピングに関してはわずかな違いがありますが、ほとんど同じセキュリティリスクにさらされるなど、ほとんど同じように動作します。
let str = "12/5*9+9.4*2"
let res1 = eval(str)
console.log('res1:', res1)
let res2 = (new Function('return '+str)())
console.log('res2:', res2)
数字を解析してからスイッチで操作を分離し、それらを作成するなどのレトルトを行うことはできません。それ以外は、この場合はevalを使用します。
それは次のようなものです(実際の実装は、特に括弧の使用を検討する場合、多少複雑になりますが、アイデアは得られます)
function operate(text) {
var values = text.split("+");
return parseInt(values[0]) + parseInt(values[1]);
}
alert(operate("9+2"));
それでも、文字列のソースを信頼できるのであれば、evalを使用することが最善の選択だと思います。