JSLint“eval是邪恶的。”备择方案

时间:2012-10-30 20:43:01

标签: javascript eval dry jslint

我有一些在客户端(浏览器)和服务器上运行的JavaScript函数(在Java Rhino上下文中)。这些是小函数 - 基本上很少有很好的定义器,不依赖于全局或闭包 - 自包含和可移植。

以下是一个例子:

function validPhoneFormat(fullObject, value, params, property) {
    var phonePattern = /^\+?([0-9\- \(\)])*$/;
    if (value && value.length && !phonePattern.test(value))
        return [ {"policyRequirement": "VALID_PHONE_FORMAT"}];
    else
        return [];
}

为了保持DRY,我的服务器代码获取每个函数的句柄并调用它们上的toString(),将它们作为JSON对象的一部分返回给浏览器。像这样:

      { "name" : "phoneNumber",
        "policies" : [ 
            { "policyFunction" : "\nfunction validPhoneFormat(fullObject, value, params, property) {\n    var phonePattern = /^\\+?([0-9\\- \\(\\)])*$/;\n    if (value && value.length && !phonePattern.test(value)) {\n        return [{\"policyRequirement\":\"VALID_PHONE_FORMAT\"}];\n    } else {\n        return [];\n    }\n}\n"
            }
          ]
      }

我的浏览器JS代码然后接受此响应,并在该上下文中创建此函数的实例,如下所示:

eval("var policyFunction = " + this.policies[j].policyFunction);

policyFailures = policyFunction.call(this, form2js(this.input.closest("form")[0]), this.input.val(), params, this.property.name));

这一切都很有效。但是,然后我通过JSLint运行此代码,并收到此消息:

[ERROR] ValidatorsManager.js:142:37:eval是邪恶的。

我很欣赏,eval可能很危险。但是,我不知道如何在不使用它的情况下实现这样的机制。有什么方法可以做到这一点并通过JSLint验证器吗?

7 个答案:

答案 0 :(得分:12)

我不担心,因为您只是将这些函数字符串从服务器传递到客户端,因此可以控制要评估的内容。

另一方面,如果你正朝着另一个方向前进并在服务器上进行客户传递代码的逃避,那将是一个完全不同的故事......

<强>更新

由于在注释中禁用验证选项可能会导致您错过将来的错误,我建议传递函数名称而不是整个函数,并在服务器和客户端上镜像函数库。因此,要调用该函数,您需要使用以下代码:

var policyFunction = YourLibraryName[this.policies[j].policyFunctionName];
var policyArguments = this.policies[j].policyArguments;

policyFunction.apply(this, policyArguments); 

更新2:

我能够成功地使用JSLint验证以下代码,这实际上允许您为eval适当的少数情况“关闭”验证。同时,JSLint仍然验证正常的eval调用,并且此方法的所有使用都应该为未来的开发人员抛出标记,以避免在可能/时间允许的情况下使用它/重构它。

var EVAL_IS_BAD__AVOID_THIS = eval;
EVAL_IS_BAD__AVOID_THIS(<yourString>);

答案 1 :(得分:4)

我会避免在所有情况下都使用eval。没有理由你不能围绕它编码。而不是将代码发送到客户端,只需将其托管在服务器上的一个包含的脚本文件中。

如果这不可行,您还可以使用动态生成的javascript文件,然后通过响应传递必要的参数,然后在客户端动态加载脚本。没有理由使用eval。

希望有所帮助。

答案 2 :(得分:4)

不要将函数编码为JSON中的字符串。 JSON用于内容,您会对行为感到困惑。

相反,我想你可以返回JS文件,它允许真正的函数:

 { name : "phoneNumber",
    policies : [ 
        { policyFunction : function() {
              whateverYouNeed('here');
          }
        }
      ]
  }

虽然这解决了技术问题,但它仍然不是一个好主意。


这里的真实解决方案是完全将您的逻辑移出您的内容。导入一个充满少量验证函数的JS文件,并根据JSON中的dataType属性或其他内容根据需要调用它们。如果这个功能像你说的那样小巧便携,那么完成它应该是微不足道的。

让您的数据与代码纠缠在一起通常会导致痛苦。您应该静态地包含您的JS,然后动态请求/导入/查询您的JSON数据以运行您的静态包含的代码。

答案 3 :(得分:1)

只需很少的解析就可以了:

var body = this.policies[j].policyFunction.substr;
body = body.substr(body.indexOf("(") + 1);
var arglist = body.substr(1, body.indexOf(")"));
body = body.substr(arglist.length + 1);
var policyFunction = new Function(arglist, body);

这将提供一些验证,避免eval的字面用法并与代码同步工作。但它肯定是eval伪装,并且容易发生XSS攻击。如果恶意的人可以通过这种方式加载和评估他们的代码 - 它将无法拯救你。所以,真的,就是不要这样做。添加带有正确网址的<script>标记,这肯定会更安全。嗯,你知道,更安全然后抱歉。

PS。如果上面的代码不起作用我表示道歉,它只显示意图,我没有测试它,如果我在计算括号或其他一些错误时犯了错误 - 好吧,你应该明白这一点,我不是在做广告无论如何。

答案 4 :(得分:1)

DRY绝对是我同意的东西,但是在某些情况下,复制+粘贴比引用同一段代码更有效,更易于维护。

您从编写中省下来的代码似乎等同于简洁的界面和简单的样板。如果在服务器和客户端上都使用相同的代码,则可以简单地传递该功能的共同部分,而不是整个功能。

Payload:
{
    "name": "phoneNumber",
    "type": "regexCheck",
    "checkData": "/^\\+?([0-9\\- \\(\\)])*$/"
}
if(payload.type === "regexCheck"){
    const result = validPhoneFormat(fullObject, value, payload.checkData)
}

function validPhoneFormat(fullObject, value, regexPattern) {

    if (value && value.length && !regexPattern.test(value))
        return [ {"policyRequirement": "VALID_PHONE_FORMAT"}];
    else
        return [];
}

这将使您能够从单个位置更新正则表达式。如果界面更改,则确实需要在2个地方进行更新,但是我认为这不是一件坏事。如果客户端正在运行代码,为什么要隐藏结构?

如果真的要把对象结构和模式都放在一个地方,请将其提取到一个API中。有一个“ ValidatePhoneViaRegex” api终结点,您可以将此序列化函数传递给的所有地方都将调用它。

如果这一切似乎都花了太多力气,请设置jslint忽略您的代码:

“在JSHint 1.0.0及更高版本中,您可以使用特殊的选项语法忽略任何警告。此警告的标识符为W061。这意味着您可以告诉JSHint不要使用/ * jshint发出此警告- W061 * /指令。

在ESLint中,生成此警告的规则称为n​​o-eval。您可以通过将其设置为0来禁用它,或者通过将其设置为1来启用它。“

https://github.com/jamesallardice/jslint-error-explanations/blob/master/message-articles/eval.md

与从服务器传递来执行的神奇功能相比,我更希望看到复制粘贴的代码,通用的api或接收参数以及复制粘贴的样板。

如果使用这些共享功能之一出现跨浏览器兼容性错误,会发生什么情况?

答案 5 :(得分:0)

您可以使用

setInterval("code to be evaluated", 0);

在内部,如果你传递一个字符串setInterval,它会执行一个类似于eval()的函数。

但是,我不担心。如果你知道eval()是邪恶的,并采取适当的预防措施,这不是一个真正的问题。 Eval类似于GoTo;你必须要小心并注意你正在做的事情才能正确使用它们。

答案 6 :(得分:0)

嗯,要记住的第一件事是jsLint确实指出了#34;它会伤害你的感情&#34;。它的目的是指出你不遵循最佳实践的地方 - 但是不完美的代码仍然可以正常工作;你没有强迫你遵循jsLint的建议。

话虽如此,eval 邪恶,几乎在所有情况下都总是有办法使用它。

在这种情况下,您可以使用诸如require.js,yepnope.js之类的库或其他用于单独加载脚本的库。这样您就可以动态地包含所需的javascript函数,而无需eval()

可能还有其他一些解决方案,但这是第一个出现在我脑海中的解决方案。

希望有所帮助。