Javascript 在执行从ajax调用返回的js时避免eval

Javascript 在执行从ajax调用返回的js时避免eval,javascript,Javascript,我想进行一个ajax调用,该调用将返回一个json对象。这个JSON对象的属性之一是要在客户机中执行的函数的字符串。我意识到使用eval很容易解决这个问题,但是看到eval的优点,我宁愿避免它。我的问题是: 我是否可以从服务器返回一些js代码,并在不使用eval的情况下执行它 根据要求,以下是一些示例代码: 服务器(Node.js): 客户: $(document).ready(function() { $.ajax({ url: '/testPack',

我想进行一个ajax调用,该调用将返回一个json对象。这个JSON对象的属性之一是要在客户机中执行的函数的字符串。我意识到使用eval很容易解决这个问题,但是看到eval的优点,我宁愿避免它。我的问题是:

我是否可以从服务器返回一些js代码,并在不使用eval的情况下执行它

根据要求,以下是一些示例代码:

服务器(Node.js):

客户:

$(document).ready(function() {
    $.ajax({
        url: '/testPack',
        success: function(data) {
            $('body').append($(data.template))
            alert(data.data.id);
            var entity = eval(data.entity);
            entity();
        }
    })
})
当然,名为entity的返回函数不会做这样愚蠢的事情,它会公开返回小部件的API

为了澄清,我希望避免对javascript本身进行单独调用。我宁愿将它与要呈现的模板和数据捆绑在一起。

不,根据定义不能这样做,因为JavaScript函数不是有效的JSON。请参见此处的规范:


如果您返回一个字符串,那么它就是:一个字符串。没有
eval
,您无法对其进行评估。您可以调用任何您想要返回的内容,但请不要将其称为JSON。

您有一些示例案例吗?我能想到的是,你可以在你的js文件中有一个常规函数,你的服务器会返回一些参数让你的函数执行。您甚至可以指定要使用的函数!(这不是很神奇吗?)


最简单的方法不是通过ajax调用服务器,而是在页面上创建一个新的脚本标记,url指向一个RESTful web服务,该服务将输出纯JavaScript(而不是JSON)。这样,您的输出将由浏览器直接评估,而无需使用eval

我想对我的答案做一点扩展: 要解决在全局上下文中运行脚本的问题,可以使用一些技巧。例如,当您将脚本标记添加到头部时,可以将onload事件(或者更确切地说是伪onload事件,因为IE不支持脚本标记上的onload)绑定到头部,并且如果来自服务器的响应将始终包装在具有已知名称的函数中,则可以从对象中应用该函数。下面的示例代码(这只是一个示例):

服务器响应应如下所示:

function serverResponse()
{
    alert(this.name);
}

window.callMe();
在这种情况下,serverResponse()中的所有内容都将使用对象作为“this”。现在,如果以这种方式修改服务器响应:

function serverResponse()
{
    this.serverJSONString = { "testVar1": "1", "testVar2": 2 };

    function Test()
    {
        alert("From the server");
    }

    Test();
}

window.callMe();
您可以使用一个响应从服务器返回多个内容。如果您不喜欢只设置变量,那么在主对象中创建一个函数来处理JSON字符串,您可以通过从响应中调用该函数来提供该字符串

正如你所看到的,这一切都是可行的,看起来确实不漂亮,但话说回来,你试图做的一开始并不漂亮

另外,仅仅在标签中插入一个字符串对IE不起作用,它不允许你这样做。如果您不必支持IE,那么只需在新创建的脚本标记中插入服务器响应就可以了


请不要按原样使用此代码,因为我没有花太多时间编写它。这很难看,但仅仅是一个例子:-)

下面是一个例子,我认为这是如何工作的

json
对象表示从服务器返回的内容。
c
d
属性包含函数名作为字符串。如果这些函数是页面中存在的其他对象的属性,那么您应该能够使用对象[“属性”]访问器调用它们

请参阅它在JSFIDLE上的工作:


是的,有一种方法,但它的缺点与
eval
完全相同

您可以使用
函数
构造函数创建一个新函数,然后调用它。例如:

new Function(code)();

你可以使用谷歌在谷歌图表上的技巧

<html>
<head>
  <script>
   function onWorkDone(data) {
    console.log(data);
   }
  </script>
  <script src="callback.js"></script>
</head>
</html>
基本上,当
doWork
完成时,脚本将调用
onWorkDone
。您可以在此处看到一个工作示例:

不使用eval的原因 嗯,你自己已经说过了。不要使用eval。但你对原因的理解是错误的

正是不是评估是邪恶的。你把理由弄错了。撇开性能考虑不谈,这种方式使用eval允许草率的程序员在客户机上执行从服务器传递的代码。请注意“从服务器传递”部分

为什么从不执行从服务器传递的代码 为什么不执行从服务器传递的代码(顺便说一句,这正是您计划要做的)

当浏览器在网页上执行脚本时,只要该网站是有效的(即真正属于你的网站,而不是试图欺骗用户的假装属于你的恶意软件网站),就可以合理地确保浏览器运行的每一段代码都是你自己编写的

黑客的天堂——脚本注入攻击 现在,如果您正在将数据从服务器传递到web应用程序,并且该数据包含可执行函数,那么您就是在自找麻烦。在数据从服务器传输到客户端浏览器的漫长而曲折的过程中,它穿越了被称为互联网的西部,也许是通过多层代理、过滤器和转换器,其中大部分是你无法控制的

现在,如果黑客隐藏在中间某个地方,从服务器获取数据,将代码修改为那些功能非常糟糕的东西,并将其发送到客户端,然后客户端浏览器获取数据并执行代码。瞧!坏事发生了。更糟糕的是:你(在服务器端)永远不会知道你的客户端被黑客攻击了

这称为“脚本注入攻击”,是一种严重的安全风险

因此,规则是:永远不要执行从服务器返回的函数

仅从服务器传递数据 如果你只接受dat
function serverResponse()
{
    this.serverJSONString = { "testVar1": "1", "testVar2": 2 };

    function Test()
    {
        alert("From the server");
    }

    Test();
}

window.callMe();
// This function is a child of the window object
window.winScopedFunction = function() {
    alert("ROCK THE WIN");    
}

// This function is a child of another object
var myObject = {
  myFunction : function() {
    alert("ROCK ON");
  }
};

// pretend that this json object was the result of an ajax call.
var jsonResultFromServer= {
    a : 1,
    b : 2,
    c : "myFunction", 
    d : "winScopedFunction" 
};

// you can call the local functions like so
myObject[jsonResultFromServer.c]();

window[jsonResultFromServer.d]();
new Function(code)();
<html>
<head>
  <script>
   function onWorkDone(data) {
    console.log(data);
   }
  </script>
  <script src="callback.js"></script>
</head>
</html>
function doWork(callback) {
  callback({result: 'foo'});
}
doWork(onWorkDone);
$functions=array(
    'showMessage' => 'function(msg){ alert(msg); }',
    'confirmAction' => 'function(action){
                          return confirm("Are you sure you want to "+action+"?");
                        }',
    'getName' => 'function getName(){
                    return prompt("What is your name?");
                  }'
);

$queried = explode($_REQUEST['f']);

echo 'var FuncUtils = {'; // begin javascript object

$counter=1;
foreach($functions as $name=>$function){
    if(in_array($counter, $queried))
        echo '"'.$name.'":,'.$function.',';
    $counter++;
}

echo '"dummy":null };'; // end javascript object
{
    "action" : ['confirmAction','exit']
}
$(document).ready(function(){
    $.getScript('functions.js.php?f=1,3');
});

function onBeforeExit(){
    $.getJSON('data5.json', function(data) {
        var func = data.action.shift();
        FuncUtils[func].apply(null, data.action);
    });
}