The Magic Eval

One of the coolest aspects of Javascript is the eval function. It gives you the option of executing code composed on the fly or received as the result of an XMLHttpRequest.

But eval can be tricky and extremely frustrating. Let’s dive into some of the most critical aspects…

What is eval?

Most simply, eval is a method on the global object (AKA a global function) accepting a single string argument which it executes as either a statement (or block of statements) or an expression. If the argument represents an expression, eval returns the result of the expression. However, if the argument represents a statement or block of statements, the return value is undefined (it might be literally the undefined value or another value, it’s undefined).

Common uses of eval:

  1. Storing the name of a function — in some cases, when writing reusable code you may know the name of the function you wish to call, but that function may not have been defined yet. Or the function may never be defined. This only becomes problematic when the name of the function is determined based on other conditions: concatenated based on element IDs or the value associated with a pop-up list option.

  2. Executing code supplied by a server — In the realm of Ajax, this is all the rage. In fact this entire article grew out of limitations I was experiencing with Ajax. Most libraries that do this impose weird limitations on the coding style supported by the Javascript they eval. A good example is the Ajax library included in Apple’s Dashboard Widget library.

  3. Executing user supplied code — Don’t do this. It’s a really bad idea because you have no way of vetting the code a user will give you. (Of course, my little debugger allows this…)

The first and second uses of eval are quite common, and I hope like hell the third use is rare, but I wouldn’t be surprised if it were common.

Scoping of evaluated code

One of the interesting aspects of eval is that the executed code has access to all the variables and functions defined in the enclosing scope. The following example will display the value of the local foo variable.

var foo="bar";

eval( "alert(foo)" );

Isn’t that interesting?

More interesting, you can declare variables and functions in the calling scope using an eval statement:

function go()
{
    eval( "function bar() { return 'bar'; }" );
    alert( bar );
}

This example will actually display the source code of the bar function. And I could call the bar function just like I would any other. However, the bar function is only valid during the execution context of the go function. I can’t call bar from anywhere else.

The functions and variables declared in the eval statement can live beyond the execution context of the eval statement, but you have to stash them somewhere. This is easy if you know the names of the functions and variables, but impossible if you don’t.

Eval in the global scope

When Javascript code is evaluated in the global scope, the same rules of scoping apply. But the code exists in the global scope. This means its lasts forever. And fortunately, this is the desired behaviour for script tags in HTML code received via XMLHttpRequest.

But how can you cause code to be evaluated in the global scope without actually evaluating it in the global scope? Use the setTimeout method. In Web browsers, the global scope is the same as the window object. Now normally, the activation object (the local scope) is an entirely different object from the object returned by the this operator, but when a function is declared in the global scope, special rules apply.

Quoting from the Javascript spec:

10.2.1 Global Code

  • The scope chain is created and initialised to contain the global object and no others.

  • Variable instantiation is performed using the global object as the variable object and using property attributes { DontDelete }.

  • The this value is the global object.

Thanks to a bug, you won’t be able to iterate global functions using Internet Explorer. Other browsers seem to work fine.

Consider the following example:

function evalInGlobalScope()
{
    var fn= "function bar() { return 'bar'; }";

    setTimeout( "eval('" + fn.replace( /'/g, "'" ) + "')", 0 );
}

function testFunction()
{
    alert( bar );
}

The function evalInGlobalScope calls the window object method setTimeout and passes a Javascript expression containing a call to eval and the code we’d like evaluated in the global scope. The timeout is set to occur after 0 seconds have elapsed (basically, as soon as possible). Later, when calling testFunction, the injected function is now available.

Update: The discussion continues in Going Global.

11 Comments

  1. Brandon Black
    Posted 4 Oct 2005 at 11:31 am | Permalink

    I’ve been solving the same problem recently, and it can be done even simpler. setTimeout() itself evals a string argument, therefore there’s no need for the escaping of single-quotes via replace() or the extra layer of eval, just do setTimeout(codestring,0); where codestring is a variable containing the js code from the tags. Here was the complete thing I used in my ajax library:

    // Strip  elements out of an XML document retreived via xmlHttpRequest,
    // returning them as an array of strings
    
    function ajax_getscripts(indoc) {
      var outscripts = [];
    
      var inelems = indoc.getElementsByTagName('script');
      for(var i = 0; i I've been solving the same problem recently, and it can be done even simpler.  setTimeout() itself evals a string argument, therefore there's no need for the escaping of single-quotes via replace() or the extra layer of eval, just do setTimeout(codestring,0); where codestring is a variable containing the js code from the  tags.  Here was the complete thing I used in my ajax library:
    
    // Strip  elements out of an XML document retreived via xmlHttpRequest,
    // returning them as an array of strings
    
    function ajax_getscripts(indoc) {
      var outscripts = [];
    
      var inelems = indoc.getElementsByTagName('script');
      for(var i = 0; i < inelems.length; i++) {
        var script_children = inelems[i].childNodes;
        var script_text='';
        for(var j = 0; j < script_children.length; j++) {
          script_text += script_children[j].data;
        }
        outscripts.push(script_text);
        inelems[i].parentNode.removeChild(inelems[i]);
      }
      return outscripts;
    }
    

    And then, after loading the new XML document over the network, but before inserting it into the main document tree, do:

    var ajscripts = ajax_getscripts(xhttp.responseXML);
    

    And finally, after you’ve loaded the new XML document/fragment into the main document tree, execute the code like so:

    for(var i = 0;i<ajscripts.length;i++) { setTimeout(ajscripts[i],0); }
    

    Everything works “as it should” AFAIK, even immediately executed code (outside of subroutine definitions) that accesses elements in the newly loaded fragment and/or the original document.

    The only thing that doesn’t work “as it should” is that onLoad attributes aren’t executed for elements in the newly received document. It would seem relatively trivial to add this support, but there are some corner cases (like the use of “this” in the context of an onLoad code string) that make it too tricky to be worth the effort IMHO. Anything you could do from onLoad can be done from within an appropriately placed node anyways.

  2. Brandon Black
    Posted 4 Oct 2005 at 11:33 am | Permalink

    Something with this Markdown stuff doubled-up part of my post when I edited it, it should be obvious what :)

  3. Posted 4 Oct 2005 at 12:57 pm | Permalink

    Brandon, thanks for pointing this out. I’m ashamed to have missed it. (And, I fixed the formatting problem with your previous post.)

  4. Mat Frédéric
    Posted 10 Mar 2006 at 8:01 am | Permalink

    you should try this function which gives access to the global scope in internet explorer:

    window.execScript(content,”javascript”);

  5. Posted 10 Mar 2006 at 3:58 pm | Permalink

    Mat, unfortunately, your code only works in Internet Explorer. Brandon’s suggestion works in all browsers, which is really important for almost any credible Javascript library.

    It can be really easy to fall into the trap of using Microsoft-only features. For example, element.children is the MS-only version of element.childNodes. It returns only elements instead of elements and text nodes, and is a little more convenient than childNodes.

    Unfortunately, children isn’t supported under Mozilla. So you either have to code things twice (not a good idea in my book) or code to the standard. Of course, bugs in any browser often require a second or possibly more code paths.

  6. Posted 15 May 2006 at 9:11 am | Permalink

    Brandon & Jeff, thanks for the trick. However, it did not work directly for me. I had to do several changes:

    1/ simplify a lot ajax_getscripts(), that finally looks like:

    function ajax_getscripts(indoc) { var outscripts = []; var inelems = indoc.getElementsByTagName(’script’); for(var i = 0; i < inelems.length; i++) { outscripts.push(inelems[i].innerHTML); } return outscripts; } In fact, I don’t really understand why Brandon’s is so complicated (might be related to the point 2/ below, though)

    2/ modified the way we send the data to it. Brandon wrote

    var ajscripts = ajax_getscripts(xhttp.responseXML);

    which evaluates to an empty string under IE 6 (maybe others, too) so I had to first assign the whole HTML block to the DIV I want to fill, and then pass this DIV to ajax_getscripts.

    3/ The functions in my tag were still not present in the global context in IE, but were in Firefox. The code was executed correctly (validated with printf()s — sorry, alert()s) but would not go into the damn global context. Thus, I created a singleton

    g_functionCatalog = new Object() // created in the global scope, only once (never gets resent through ajax async requests)

    and then I bound all the functions returned by the further XmlHttpRequests to that object, e.g.

    g_functionCatalog.myFunctionReturnedByAjax = function() { [...] }

    This works. The drawback is that I have to call those functions with the syntax functionCatalog.myFunctionReturnedByAjax() instead of simply myFunctionReturnedByAjax().

    Anyway, thanks a lot for the info. I had been looking for 2-3 hours how to promote a javascript function to the global scope; your site has just given me the precise answer.

  7. Posted 5 Jul 2006 at 3:05 pm | Permalink

    I was just struggling with IE not loading eval’ed Javascript into the global space and I didn’t know about the execScript IE function. Thanks! I ended up with the following code which works on all the browsers I tried:

    if (window.ActiveXObject) { window.execScript(jsCode); } else /* (window.XMLHttpRequest) */ { window.eval(jsCode); }

    Checking for ActiveXObject is analogous to most of the AJAX getHttpRequest implementations I’ve seen.

  8. Posted 5 Jul 2006 at 5:21 pm | Permalink

    Eric, that’s a good idea. I’m not certain why that didn’t occur to me. It’s great to be able to have a complete solution, even for MSIE.

  9. raulshred
    Posted 21 Jul 2006 at 2:30 am | Permalink

    This last method don’t rules in Konqueror :(

    if(MSIE) { window.execScript(sFunction,’javascript’); } else { setTimeout(’eval(”+sFunction.replace(/’/g,”’)+”)’,0); }

    This code rules in MSIE,Gecko,Opera,Safari,Konqueror.

  10. natasha kuzmanoska
    Posted 29 Jul 2006 at 8:35 am | Permalink

    If you change the way you are declaring functions you can acces them globaly without problem, for the global variables just do not use var;

    function go(){ eval(”bar = function(){return ‘bar’}”); eval(”b = 22;”); eval(”var a = 22;”); //for this context only }

    function go1(){ var b = 33; // for this context } go(); go1();

    alert(bar()); alert(b); Ok alert(a); does not work

  11. natasha kuzmanoska
    Posted 29 Jul 2006 at 9:49 am | Permalink

    for firefox you can use window._content.eval() if you want to get global context