The eval method — which takes a string containing JScript code, compiles it and runs it — is probably the most powerful and most misused method in JScript. There are a few scenarios in which eval is invaluable. For example, when you are building up complex mathematical expressions based on user input, or when you are serializing object state to a string so that it can be stored or transmitted, and reconstituted later.
However, these worthy scenarios make up a tiny percentage of the actual usage of eval. In the majority of cases, eval is used like a sledgehammer swatting a fly — it gets the job done, but with too much power. It’s slow, it’s unwieldy, and tends to magnify the damage when you make a mistake. Please spread the word far and wide: if you are considering using eval then there is probably a better way. Think hard before you use eval.
Let me give you an example of a typical usage.
<span id="myspan1"></span> <span id="myspan2"></span> <span id="myspan3"></span> function setspan(num, text) { eval("myspan" + num + ".innerText = '" + text + "'"); }
Somehow the program is getting its hands on a number, and it wants to map that to a particular span. What’s wrong with this picture?
Well, pretty much everything. This is a horrid way to implement these simple semantics. First off, what if the text contains an apostrophe? Then we’ll generate
myspan1.innerText = 'it ain't what you do, it's the way thacha do it';
Which isn’t legal JScript. Similarly, what if it contains stuff interpretable as escape sequences? OK, let’s fix that up.
eval("myspan" + num).innerText = text;
If you have to use eval, eval as little of the expression as possible, and only do it once. I’ve seen code like this in real live web sites:
if (eval(foo) != null && eval(foo).blah == 123) eval(foo).baz = "hello";
Yikes! That calls the compiler three times to compile up the same code! People, eval starts a compiler. Before you use it, ask yourself whether there is a better way to solve this problem than starting up a compiler!
Anyway, our modified solution is much better but still awful. What if num is out of range? What if it isn’t even a number? We could put in checks, but why bother? We need to take a step back here and ask what problem we are trying to solve.
We have a number. We would like to map that number onto an object. How would you solve this problem if you didn’t have eval? This is not a difficult programming problem! Obviously an array is a far better solution:
var spans = new Array(null, myspan1, myspan2, myspan3); function setspan(num, text) { if (spans[num] != null) spans[num].innertext = text; }
Since JScript has string-indexed associative arrays, this generalizes to far more than just numeric scenarios. Build any map you want. JScript even provides a convenient syntax for maps!
var spans = { 1 : mySpan1, 2 : mySpan2, 12 : mySpan12 };
Let’s compare these two solutions on a number of axes:
Debugability: what is easier to debug, a program that dynamically generates new code at runtime, or a program with a static body of code? What is easier to debug, a program that uses arrays as arrays, or a program that every time it needs to map a number to an object it compiles up a small new program?
Maintainability: What’s easier to maintain, a table or a program that dynamically spits new code?
Speed: which do you think is faster, a program that dereferences an array, or a program that starts a compiler?
Memory: which uses more memory, a program that dereferences an array, or a program that starts a compiler and compiles a new chunk of code every time you need to access an array?
There is absolutely no reason to use eval to solve problems like mapping strings or numbers onto objects. Doing so dramatically lowers the quality of the code on pretty much every imaginable axis.
It gets even worse when you use eval on the server, but that’s another post.
Notes from 2020
This was my first deliberately-multi-episode topic.
There were many great comments on this article on the original blog site; to summarize a few of them:
- There are a number of scenarios where you want to dynamically create a new function, but “new Function” is the appropriate choice rather than “eval” most of the time.
- However, the scoping rules for “new Function” and “eval” are different — thanks, JavaScript — and so sometimes there are scenarios where you are forced to eval a new function.
- I was not then and am not now an expert on the browser’s object model. I have many times noted the irony that as a developer of the JS compiler, I was an expert on the inner workings of the JS compiler, and not on how it was used in practice. A reader pointed out that none of my solutions were good practice compared with the expediency of:
var span = document.all("myspan" + num); if (span != null) span.innertext = text;
or, equivalently, getElementById, on browsers which supported it at the time.
- One reader pointed out that good enough is, by definition, good enough. We’re writing scripts here; if they are fast enough, robust enough, debuggable enough, and so on, then who cares if there is a better way? Why take the time to learn the right way to do something when there is a good enough expedient way you already know? My answer to those (entirely reasonable) questions would be: JS code is no longer trivial little ten-liner scripts to automate UI elements. We have serious engineering problems to solve, and we should take a disciplined, best-practices-engineering stance by default. In short: move your standards of what is good enough! That benefits all the stakeholders.
- I alluded to, but did not call out explicitly, that eval can be used to deserialize JSON. But it is not quite so easy as that! In 2007 The Curious Schemer published a great follow-up to this article pointing out some of the unexpected ways in which eval does not match JSON.
- Also in 2007, my good friend Rob Hahn and I discussed some alternatives to eval for a tricky problem he had in function creation.
Pingback: Eval is evil, part two | Fabulous adventures in coding