关于javascript:eval可以访问外部函数吗?

Can an eval access external functions?

我的公司允许我们在网上的javascript编辑器中编写代码。其他库是预加载的,因此我们编写的代码可以访问这些库。

具体来说,我们可以在代码中使用underline.js和jquery.js函数。我们也可以使用我们自己的库graphie.js。

为了节省我自己的时间,我慢慢地建立了自己的一组函数,我将它们复制并粘贴到我编写的每一个代码中。这组函数现在太长了,我想从外部获取它(以节省空间等)。

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

我试过上面的代码,但它太好了,不可能是真的。jquery函数getScript似乎以自己独立的单元运行myfunctions。这失败了,因为myfunctions在其中使用graphie.js函数。

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

上面的代码获取并成功地获取了eval的我的代码(我配置了我的服务器这样做)。也太好了,不可能是真的。我的代码中的任何jquery和underscode函数实际上都可以工作。但是我代码中的任何图形函数都会导致错误。


而不是

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

尝试

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

这样,eval函数将在与其余代码相同的范围内执行,而不是在jquery范围内执行。获取并执行代码后,可以继续执行其余代码:

1
2
3
4
5
6
7
8
$.get( 'url/to/myfunctions', function(code) {
  eval(code);
  callback();
});

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

解释

为了解释的目的,让我们使用这个简化的环境模型,在该模型中执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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); } );
  })();
})();
1
2
3
4
5
6
7
The output:

  function log(msg) {
    var el = document.createElement("li");
    el.appendChild(document.createTextNode(msg));
    output.appendChild(el);
  }

如您所见,传递给$.get的函数在其主体内执行。如果您只将eval传递给$.get,那么您就不会捕获局部变量graphie,这对评估代码是不可见的。通过将eval包装在匿名函数中,可以捕获对局部变量graphie的引用,然后评估代码就可以看到该引用。


感谢大家的帮助,这是一个行之有效的解决方案…

myFunctions.js文件必须包装在一个函数中:

1
2
3
4
5
6
7
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.
}

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

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

不知何故,作为evaled的代码没有访问全局变量mygraphievar的权限,这就是为什么它必须被传入而不是evaled代码的一部分(这里我犯了一个小错误)。

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

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

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


假设您拥有对该脚本的Ajax访问权限(因为在所示示例代码中,.get就是这样做的),您可以尝试使用jquery的.html()来放置脚本,该脚本应该在页面的变量环境中执行。

1
2
3
4
5
6
7
8
9
10
$.ajax({
        url: 'url/to/myfunctions.js',
        type: 'GET',
        success: function (result) {
            var script = '<scr'+'ipt>'+result+'</scr'+'ipt>';
            var div = $("");
            $("body").append(div);
            div.html(script);
        }
});

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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运行?


我建议不要使用eval。但是,您可以遵循以下模型。

首先,在您的myFunctions.js中,将所有代码包装成一个函数。

1
2
3
(function(_, $, graphie) {
   // declare all your functions here which makes use of the paramters
}) // we will be calling this anonymous function later with parameters

在拿到剧本之后你可以

1
2
3
4
5
6
$.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作为参数,但我不确定。所以把你正确的图形变量放在这里。