替代邪恶的求值-关系运算符

Alternative to Evil Eval - relational operators

本文关键字:关系 运算符 邪恶      更新时间:2023-09-26

作为输入验证的一种形式,我需要强制将像'9>6'这样的字符串计算为布尔值。

除了计算字符串外,我似乎找不到解决方法。

我总是听说eval的邪恶(特别是当我验证表单输入时),关于它可以评估任何脚本和性能问题的事实。

但是…

在我的情况下(处理关系操作符)有任何替代方案吗?

var arr = ['<9', '>2'];
var check = function (a) {
    return arr.every(function (x) {
            var string = '';
            string += a + x;
            try {
                return eval(string);
            } catch (e) {
                return false;
            }
        });
    };
console.log(check('3'))

我不会说eval本质上是邪恶的,我只是说它有它的优点和缺点。在我的实践中,我很少遇到它的优点很多的情况。

eval的两个主要问题:
  1. 它需要运行时解释,即javascript引擎的字符串处理和解释。它是缓慢的。正常的javascript以标记化的形式运行。
  2. 如果你使用eval,你也开始了一个游戏,你想要阻止插入任何有害的代码,而不降低功能,黑客试图找到一个方法通过你的块。你唯一能确定你没有输的就是你的网站直到现在才被破解。你根本不可能知道你赢了。

但是:eval主要运行在浏览器端,或者处理用户输入数据(由同一用户创建),或者服务器端生成的服务器数据。

你的情况的解决方案是显而易见的:使用硬OO的东西,例如函子。

function comparator(lighter, num) {
  return function(x) {
    return lighter ? (x<num) : (x>num);
  }
}
var arr = [ comparator(true, '9'), comparator(false, '2') ];
var check = function(a) {
  return arr.every(function(comp) { return comp(a); });
}
console.log(check('3'));

在第一点上,它比你的版本复杂得多,但这只是因为你习惯了求值,而不是复杂的函子解决方案。事实上,解决方案的复杂性是非常相似的。

我认为有三点需要改进:

  • 不要将a值字符串化并将其连接到表达式中,而应该直接引用a变量,以便与值的真实形式进行比较。否则你可能会得到奇怪的行为。

    return eval("a " + x);
    
  • 由于您可能多次使用check,因此您不应该每次都调用eval(甚至每次调用多次)。您可以将字符串组合成一个大的&&表达式,甚至更好的是,您可以事先将条件编译为单个函数,这样您就完全不必从check中调用eval

  • 你应该考虑使用Function构造函数而不是eval

使用这些,您的代码可能看起来像这样:
var arr = ['<9', '>2'];
var check = new Function("a", "return "+arr.map(function(x) {
    return "a"+x;
}).join(" && ")+";");
console.log(check.toString()); // function anonymous(a) { return a<9 && a>2; }
console.log(check('3'));

或:

var arr = ['<9', '>2'];
var fns = arr.map(function(x) {
    return new Function("a", "return a "+x+";");
});
function check(a) {
    return fns.every(function(f) {
        return f(a);
    });
}
console.log(check('3'));

当然,我希望arr不是由用户控制的(或者更糟,不是当前用户控制的);如果您希望用户输入这些条件,您应该确保将它们显式地列入白名单,或者可以立即使用更复杂的表达式语法/解析器/求值器。
除非是这种情况,否则eval(或者就此而言,Function)并不完全是邪恶的,它只是缩短(并可能简化)您的代码。您也可以编写完整的函数(就像Nit和peterh建议的那样)。

一种选择是定义辅助函数,但是这种方法有多有用很大程度上取决于您想要在更大的范围内实现什么。

function geq(n) {
    return function(m) {
        return m >= n;
    }
}
function leq(n) {
    return function(m) {
        return m <= n;
    }
}
var arr = [leq(9), geq(2)];
function check(n) {
    arr.forEach(function(checkAgainst) {
        console.log(checkAgainst(n));
    });
}