一个eval可以访问外部函数吗?

3

我的公司允许我们在线使用JavaScript编辑器编写代码。其他库已经预加载,因此我们编写的代码可以访问这些库。

具体来说,我们可以在代码中使用Underscore.js 和 jQuery.js函数。我们还可以使用自己的库Graphie.js。

为了节省时间,我逐渐建立了自己的函数集,并将其复制粘贴到我编写的每个代码中。该函数集现在变得如此之长,以至于我想从外部获取它(以节省空间等)。

$.getScript( 'url/to/myfunctions.js' )

我尝试了上面的代码,但它太完美了,以至于不可信。这个jQuery函数getScript似乎会将我的函数作为独立单元运行。这会导致失败,因为我的函数在其中使用了我们的Graphie.js函数。

$.get( 'url/to/myfunctions', eval )

上述代码获取并成功地eval了我的代码(我配置了服务器来执行此操作)。好得令人难以置信。我的代码中的任何jQuery和Underscore函数都可以正常工作。但是,我的代码中的任何Graphie函数都会导致错误。


3
请勿这样做,对远程脚本使用eval不是一个好的设计模式。最好的方式是将代码作为原始JavaScript的一部分,并根据从远程获取的数据来控制程序,否则你会被迫使用诸如注入脚本标签之类的技巧。更不用提涉及内容源策略的噩梦了。这里有一些想法:https://dev59.com/c3VD5IYBdhLWcg3wGXpI#87260 - Sukima
你能否利用jQuery的可扩展性,将你的.js代码转化为一个jQuery插件(或者你使用的任何库)。这样一来,你就可以将你的代码“注入”到你的编辑器中,并像使用jQuery方法一样处理它。 - Michael
只要graphie.js已经加载,getScript函数应该可以正常工作。 - dandavis
1
你是想让编辑器中的代码能够访问函数,而不是页面托管编辑器吗? - Jared Smith
1
我猜我的问题实际上是:您正在将代码输入托管在网页上的编辑器中,目的是什么?在页面上执行编辑器中的代码作为文本编辑器兼沙盒?如果编辑器中运行的代码具有与托管页面相同的全局命名空间,那么阿米尔·波波维奇建议的应该可以正常工作。当然,这也意味着您已经尝试过的内容不应该失败。如果您放入编辑器的代码与托管页面上的js环境分开运行,则无法向该页面注入脚本标记。 - Jared Smith
显示剩余6条评论
4个回答

3

与其

$.get( 'url/to/myfunctions', eval );

尝试

$.get( 'url/to/myfunctions', function(code) { eval(code); } );

这样一来,eval函数就会在你的代码的同一作用域中执行,而不是在jQuery的作用域中执行。代码被获取和执行后,你可以继续执行其余部分的代码:

$.get( 'url/to/myfunctions', function(code) {
  eval(code);
  callback();
});

function callback() {
  // Your code goes here
}

解释

为了解释起见,我们使用这个简化的环境模型来执行您的代码:

// JQuery is defined in the global scope
var $ = {
  get: function( url, fn ) {
    var responses = {
      "url/to/myfunctions": "try {\
        if(graphie) log('Graphie is visible.');\
      } catch (e) {\
        log('Graphie is not visible. (' + e + ')');\
      }"
    }; fn( responses[url] );
  }
};

(function() {
  // Graphie is defined in a local scope
  var graphie = {};
  (function() {
    // Your code goes here
    $.get( "url/to/myfunctions", eval );
    $.get( "url/to/myfunctions", function(code) { eval (code); } );
  })();
})();
The output: <ol id="output"></ol>
<script>
  function log(msg) {
    var el = document.createElement("li");
    el.appendChild(document.createTextNode(msg));
    output.appendChild(el);
  }
</script>

正如您所看到的,传递给$.get的函数在其主体内执行。如果您仅将eval传递给$.get,那么您无法捕获本地变量graphie,这样在评估的代码中就看不到它了。通过将eval包装在一个匿名函数中,您可以捕获对本地变量graphie的引用,然后在评估的代码中就可以看到它。


1
那么仅在那个微小的回调作用域内评估代码会有什么优势呢? - Bergi
@Bergi:新创建的匿名函数可以访问操作者代码所在的整个作用域。通过仅传递一个 eval 引用到 $.get 方法,您可以在 jQuery 库的作用域中执行它,这可能与 Graphie 库隔离开来。这只是一种猜测,但这很可能是问题所在。 - Witiko
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Bergi
Graphie和op的代码可以作为匿名函数的一部分执行,因此在全局范围内不可见。 - Witiko
但在这种情况下,回调函数中的 eval 将无法帮助(除非它引入的所有变量都是预先声明的)。 - Bergi
是的,必须等待脚本通过 AJAX 加载并执行。不过我相信 OP 已经非常清楚了,因为这不是他所遇到的问题的一部分。 - Witiko

0
我建议不要使用eval。然而,你可以遵循以下模式。
首先,在你的myFunctions.js中,将所有代码封装到一个函数中。
(function(_, $, graphie) {
   // declare all your functions here which makes use of the paramters
}) // we will be calling this anonymous function later with parameters

然后在获取脚本之后,您可以执行以下操作

$.get( 'url/to/myfunctions', function(fn){
    var el = document.createElement('script');
    el.type = 'text/javascript';
    el.text = fn + '(_, jQuery, Graphie);';
    document.head.appendChild(el);
});

请注意,我将 Graphie 作为参数,但我不确定是否正确。所以请在那里放入您正确的graphie变量。

2
你反对使用 eval() 的建议背后的理由是什么? - Witiko
1
将脚本获取并注入页面/评估它与直接注入没有区别。只是在前一种情况下,您会失去局部作用域,并且无法保证Graphie在全局作用域中可访问。 - Witiko
@Witiko 我的代码在全局上下文中执行。如果 graphie 变量不存在,即使是你的代码也无法工作。 - Amit Joki
除非Graphie变量是本地的,否则eval将起作用,脚本注入不会发生。 - Witiko
但这些只是字符串。您没有传递任何本地引用到注入的代码中。您只是指示它在执行后查找全局变量jQuery和Graphie。 - Witiko
显示剩余2条评论

0
假设您可以通过ajax访问此脚本(因为这是您展示的示例代码中$.get正在执行的操作),您可以尝试使用jQuery的.html()将脚本放置在页面的变量环境中以执行它。
$.ajax({
        url: 'url/to/myfunctions.js',
        type: 'GET',
        success: function (result) {
            var script = '<scr'+'ipt>'+result+'</scr'+'ipt>';
            var div = $("<div>");
            $("body").append(div);
            div.html(script);
        }
});

在内部,这个脚本最终将由jQuery的globalEval函数执行。https://github.com/jquery/jquery/blob/1.9.1/src/core.js#L577

// Evaluates a script in a global context
// Workarounds based on findings by Jim Driscoll
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
globalEval: function( data ) {
    if ( data && jQuery.trim( data ) ) {
        // We use execScript on Internet Explorer
        // We use an anonymous function so that context is window
        // rather than jQuery in Firefox
        ( window.execScript || function( data ) {
            window[ "eval" ].call( window, data );
        } )( data );
    }
}

我也在这里提出了一个与此相关的问题:为什么使用jQuery的html可以运行脚本,但使用innerHTML却不行?


0
感谢大家的帮助,这是有效的解决方案... < p > myfunctions.js文件必须包装在一个函数中:

function everything(_,$,Graphie){
    // every one of myfunctions now must be attached to the Graphie object like this:
    Graphie.oneOfMyFunctions = function(input1,input2,etc){
        // content of oneOfMyFunctions
    }
    // the rest of myfunctions, etc.
}

然后在我的代码中,我可以使用以下方式检索它:

$.get( '//path/to/myfunctions', eval )
everything(_,jQuery,mygraphievar);

某种情况下,被 eval 的代码无法访问全局变量 mygraphievar,这就是为什么它必须被传递而不是成为 eval 的一部分(这里阿米特犯了一个小错误)。

此外,everything 函数是在 $.get() 之外执行的,以便在下面的任何其他代码执行之前对 mygraphievar 进行更改。

应该注意到,$.get() 实际上是一个异步函数,并且只有在执行其他代码之后才会调用 eval。这导致代码在第一次运行时失败,但在第一次之后,函数被保存在内存中,然后一切正常工作。正确的解决方案是将我想要执行的所有代码都写在 $.get() 的回调函数中,但我太懒了。

还应该知道,使用 $.getScript() 可能有一个稍微简单的解决方案,但我没有时间验证它。


您依赖于未定义的行为。您无法保证在$.get返回时“everything”已经被定义,而您的浏览器将“/path/to/myfunctions”存储在缓存中并在发出AJAX调用后立即执行eval的事实并不是规范的一部分。如果这对您造成问题,您应该考虑使用回调而不是期望函数同步加载。 - Witiko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接