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:
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.
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.
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
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:
And then, after loading the new XML document over the network, but before inserting it into the main document tree, do:
And finally, after you’ve loaded the new XML document/fragment into the main document tree, execute the code like so:
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.
Something with this Markdown stuff doubled-up part of my post when I edited it, it should be obvious what :)
Brandon, thanks for pointing this out. I’m ashamed to have missed it. (And, I fixed the formatting problem with your previous post.)
you should try this function which gives access to the global scope in internet explorer:
window.execScript(content,”javascript”);
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.childrenis the MS-only version ofelement.childNodes. It returns only elements instead of elements and text nodes, and is a little more convenient thanchildNodes.Unfortunately,
childrenisn’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.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.
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.
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.
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.
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
for firefox you can use window._content.eval() if you want to get global context