Problems with Evals
Eval-statements provide a special kind of problem for script minifiers. For the most part, there are very few things you can do with an eval-statement that you can’t do some other, safer way. Two examples are:
- generating a JS object from a JSON string in older browsers that don’t support the JSON object, and
- wrapping modern syntax not supported on older browsers you have still to support with your code.
For an example of the latter, if you need to support WebTV (for example) and you need to use a try/catch statement in your code, you’re pretty much out of luck because the try/catch statement itself isn’t supported on that older platform and
will throw a script error when your code is parsed. Instead, you’ll have to carefully wrap your code in an eval-statement so it will parse, and try to make sure it doesn’t actually get called for WebTV clients.
Other than those two examples, there really aren’t a lot of good reasons to use eval-statements. As
Douglas Crockford has said: “eval is evil.” But people do use them sometimes. AjaxMin
by default, does not treat eval-statements any differently than any other code. That may seem like a dangerous stand to take, but I totally agree with Crockford and think eval-statements should be avoided
if at all possible. By taking this stand I am hoping to force developers to realize their code has eval-statements in it and consider them more closely and conscientiously. If you don’t use any eval-statements, or use an eval-statement only for something
like JSON evaluation, you should not have any problems with AjaxMin. If you use eval-statements for any other purpose and reference local variables and/or function names within the evaluated string, then your minified code may break and you will need to either
change your code to not use an eval-statement, or fall back to using a different eval-mode using the
–evals
command-line switch (or the CodeSettings.EvalTreatment property if you are using the DLL version).
The primary reason I don’t like eval-statements is simple run-time performance. Whenever an eval-statement is executed, a JavaScript parser needs to get spun up and parse the source string before it can be executed. That is not a free exercise. If
you want your code to operate efficiently and quickly, parsing and reparsing is not going to help you out.
And don’t forget that there are more ways to execute an “eval” than just calling the eval function. The
Function object constructor is an implicit eval – it has to parse and execute the strings passed to it in order to create the function object. Passing strings to
setTimeout and
setInterval also perform an implicit eval – every time the timer fires, the strings have to be reparsed before they can be executed. Try to avoid these situations if possible; there usually is a much cleaner way to do the same thing.
There are a few common eval-statement patterns I see in JavaScript that always make me cry when I read them. I have only come across a tiny handful of situations where an eval-statement seems like a good idea; the
vast majority of times it’s just bad coding. For instance, I see things like this a lot (and I’m pretty sure puppies somewhere die whenever code like this is released to the web):
function foo(obj, prop)
{
return eval("obj." + prop);
}
For the love of Pete, please don’t ever do that. JavaScript properties can be accessed multiple ways, and if you don’t know the name of the property at compile-time, use the
[]-operator:
function foo(obj, prop)
{
return obj[prop];
}
I also frequently see people using string arguments to
setTimeout when they want to pass parameters to the function that gets called when the timeout fires. As with most other cases of eval-statement usages, there is a better way. If you don’t understand JavaScript closures very well, I strongly suggest
you Bing
“JavaScript closures” right now and do a little reading. They are what make JavaScript a powerful programming language, and a thorough understanding of how they work will save you a lot of time and many headaches. Anyhow, let’s get to
an example. Instead of:
function foo(n, v)
{
alert(n + ": " + v);
}
var name, val;
// set name and val somehow
window.setTimeout("foo('" + name + "', '" + val + "')", 2000);
code it like:
function foo(n, v)
{
alert(n + ": " + v);
}
var name, val;
// set name and val somehow
window.setTimeout(function(){foo(name, val)}, 2000);
I won’t get into the details of how closures work in this article, but calling
setTimeout this way will call your function with the proper parameters when the timeout fires
without having to fire up the JavaScript parser again -- and you won’t have to worry about any syntax errors should the name or value variables point to a string that contains single-quotes! Of course, this sample is a little contrived;
it might be better to just inline the target function altogether. It all really depends upon your situation. The point being the use of closures in situations like this will greatly help your application.
What To Do?
Okay, so let’s say you need to use eval-statements in your code, but when you minify it with AjaxMin, your application breaks. What do you do? You need to specify a different “Eval Treatment” mode. There are three values you can
specify with the –evals
switch:
- -evals:ignore
- -evals:immediate
- -evals:safeall
The first is the default: ignore them and treat them like any other statement and hope for the best. The second one will make sure that variables and functions within the same scope as the eval-statement are not renamed, so if the eval-statement
doesn’t reference anything in a parent scope, it shouldn’t run into any problems. The last one is the nuclear-option: no variables or function will be renamed or removed not only in the current scope,
but also all parent scopes up the chain to the global scope. Use this option only if you need it, because it could result in a minified result much larger than it needs to be. But it should always work.
Of course, I’ll say it again: the best option is to not use eval-statements at all.