In a future post, I argue that the Function
constructor in JavaScript can be
used for good, solving specific problems.
Preparatory to that, in this post, I examine the eval
function and the
Function
constructor to highlight their similarities as well as their subtle
differences, and how to use them without problems.
good or bad?
As JavaScript developers we have long been warned about using the eval
,
Function
, setInterval
and setTimeout
functions, most famously by
Douglas Crockford
(scroll to the bottom).
Since then several authors have shown safe use cases for eval
and Function
:
- Simon Willison: Don't be eval
- Ben Nadel:
Faking Context In Javascript's Function() Constructor
in which he uses
eval()
inside a loop inside aFunction
constructor string. - Alex Young: [JS101: The Function Constructor}(http://dailyjs.com/2012/07/09/function-2/)
- Nicholas Zakas: eval() isn't evil, just misunderstood
- Christopher Wellons: JavaScript Function Metaprogramming
We'll list some problems with these methods - at a higher level - then proceed with the API, and how to work around the problems in code.
problems
overuse
The general case we are warned against is naive overuse. The following example
is taken from a stackoverflow thread on not
using eval
- to wit, doing this
eval('document.' + potato + '.style.color = "red"');
instead of either of these
// DOM Level 1 element access
document[potato].style.color = 'red';
// DOM Level 2 element access
document.getElementById(potato).style.color = 'red';
errors
The special case is passing in source code from over the wire - an ajax response, for example - in order to create an object and apply it to already running code. The danger is that malicious or malformed code will result in errors, causing your application to misbehave or stop behaving altogether.
In response to this issue in particular, Douglas Crockford promoted the use of
JSON.parse
and JSON.stringify
methods for processing ajax response text to
avoid evaluating JSON as JavaScript.
local scope access
eval
also has access to all variables within the calling context or execution
scope (i.e., inside the function) in which it is used. That makes it unsafe, if
you want to avoid clobbering local variables inadvertently.
Function
does not have access to the calling scope, but malicious or
malformed code will still cause problems.
global scope access
The eval
function has access to the global scope, so it can clobber any
globals as well. Function
shares this problem.
debuggers
The eval
function acts as a code generator of sorts. Code generated at runtime
is harder to debug - that is, step through with a debugger - because you can't
set break points on code that hasn't been evaluated. Function
shares this
problem.
performance
Another problem is the performance hit that eval
incurs because it must parse,
evaluate, then interpret, source code of unpredictable size - it may contain few
statements, or very many. Function
shares this problem.
That last point, that the input size is not knowable beforehand, means that code minifiers can't minify the blocks of strings ahead of time, and that runtime engines may not be able to optimize lookahead caching (a fancy way of saying, they can't compile it).
the API
eval
evaluates a string representing JavaScript code and executes it.
eval(code)
Function
can be called with or without the new
operator. Function
takes
one or more string arguments and produces a new function object. The last
argument is a string representing JavaScript code.
// no param names
var F = Function(code)
Arguments before the code are evaluated as parameter names to be applied to the new function object. This can take one of 3 forms.
// explicit param name strings
var F = Function('a', 'b', code);
// comma-separated param names in a string
var F = Function('a, b', code);
// array of param name strings
var F = Function(['a', 'b'], code);
The array signature turns out to be quite handy, not only for the param names,
var argNames = ['a', 'b'];
...but for the code or function body parts, too,
var lines = ["console.log(arguments.length);",
"console.log(a);",
"console.log(b)"];
...allowing us to create a factory that takes two arrays of strings
function factory(params, lines) {
// ...preprocessing statements here...
return Function(params || '', lines.join('\n'));
}
var test = factory(argNames, lines);
test(3, 5); // 2, 3, 5
test(88); // 1, 88, undefined
test(0, 1, 4); // 3, 0, 1
test(null); // 1, null, undefined
scope object of this
eval
In eval
, this
refers to the scope of the calling context. In general,
this
will refer to the global scope
(function testEval() {
eval('console.log(this);'); // window or global
}());
(function testEval() {
(function nested() {
eval('console.log(this);'); // window or global
}());
}());
constructor usage
Where eval
is called inside a function invoked with the new
operator, this
refers to the constructor (rather than the newly created object):
function EvalTest() {
eval('console.log(this);');
eval('console.log(this instanceof EvalTest);');
}
new EvalTest();
// EvalTest !
// true
That can be done with inline definition and instantiation as
new function EvalTest(){
eval('console.log(this);'); // EvalTest !
eval('console.log(this instanceof EvalTest);'); // true
};
(new function EvalTest() {
eval('console.log(this);'); // EvalTest !
eval('console.log(this instanceof EvalTest);'); // true
}());
new (function EvalTest(){
eval('console.log(this);'); // EvalTest !
eval('console.log(this instanceof EvalTest);'); // true
});
Function
In the Function
constructor, this
is the global scope by default. These
examples use immediate invocations of the new function object
inside a function
(function testFunction() {
Function('console.log(this);')(); // window or global
}());
as an anonymous constructor
(function testFunction() {
new Function('console.log(this);')(); // window or global
}());
inside a constructor
(new function testFunction() {
Function('console.log(this);')(); // Window or global
}());
new function testFunction() {
Function('console.log(this);')(); // Window or global
};
When the created function object is executed as a named constructor, this
refers to the instantiated object
(function testFunction() {
var F = Function('console.log(this);');
new F(); // anonymous (FF) or Object { } (webkit)
}());
(function testFunction() {
var F = Function('console.log(this.toString());');
new F(); // [object Object]
}());
If you set the scope dynamically, as with any function, using call
or apply
,
this
refers to that scope
(function testFunction() {
Function('console.log(this.id);').call({ id: 'fake' }); // 'fake'
}());
Using call
or apply
without a defined scope, this
refers by default to the
global scope
(function testFunction() {
Function('console.log(this);').apply(); // Window or global
}());
scope access
eval
Because eval
has access to the global scope, the following creates a new
global variable
(function testEval() {
eval('answer = 42');
console.log(answer); // 42
}());
console.log(answer); // 42
Because eval
has access to the calling or local scope, eval
will clobber the
variable named inside that scope without affecting the global scope
var answer = 'default';
(function testEval() {
var answer;
eval('answer = 42');
console.log(answer); // 42
}());
console.log(answer); // 'default'
Function
Function
has access to the global scope and can create or clobber global
variables
(function testFunction() {
Function('answer = 42')();
console.log(answer); // 42
}());
console.log(answer); // 42 ! here's our leak
Function
does not have access to the calling scope
(function testFunction() {
var answer = 'default';
Function('answer = 42')();
console.log(answer); // 'default'
}());
However that will create or clobber a global variable
console.log(answer); // 42 ! here's our leak
strict mode
As Nicholas Zakas argues, we can start using strict mode in ES5 runtimes to prevent the creation and/or clobbering of accidental globals.
Here's the general usage of strict mode within a function
(function testFunction() {
"use strict";
(function() {
answer = 42; // ReferenceError: assignment to undeclared variable answer
})();
console.log(answer); // n/a
}());
eval
We can use strict mode with eval
to prevent leaking and clobbering from within
a local scope
(function testEval() {
var answer;
eval('"use strict"; answer = 42');
console.log(answer); // 42
}());
(function testEval() {
"use strict";
eval('answer = 42'); // ReferenceError: assignment to undeclared variable answer
console.log(answer); // n/a
}());
(function testEval() {
eval('"use strict"; answer = 42'); // ReferenceError: assignment to undeclared variable answer
console.log(answer); // n/a
}());
Function
constructor
Because Function
does not have access to the local scope, the "use strict"
pragma must be included in the Function
body in order to prevent leaking and
clobbering from within a local scope.
This fails
(function testFunction() {
"use strict";
Function('answer = 42')();
console.log(answer); // 42
}());
console.log(answer); // 42 -- leaking
This works
(function testFunction() {
Function('"use strict"; answer = 42')(); // ReferenceError: assignment to undeclared variable answer
console.log(answer); // n/a
}());
paranoid sandbox
For runtimes that do not support strict mode you need to implement a sandbox
that cleans up any accidental or temporary globals created when running
Function
or eval
.
Here's a quick implementation of such a sandbox function
function sandbox(fn) {
// hack for cross-platform global
global = global || window;
var keys = {};
var result, k;
for (k in global) {
keys[k] = k;
}
result = fn();
for (k in global) {
if (!(k in keys)) {
delete global[k];
}
}
return result;
}
First there is be a pre-test that collects all keys currently assigned to the global namespace. After the target function is run, clean up any new keys found in the global namespace.
The following is a drastically reduced example of using it
var context = {
name: 'david',
occupation: 'typist'
};
var code = ['for (var key in context) {',
' if (context.hasOwnProperty(key)) {',
' console.log(key);',
' }',
'}'];
sandbox(function () {
Function('context', code.join('\n'))(context);
return context;
});
The key is to use Function
inside another function that is actually passed to
the sandbox
function.
debugging ~ breakpoints, etc.
I confess I do not use line debuggers when isolating problems in JavaScript, as I prefer the healthy practice of test-driven development. However, following Paul Irish's chrome dev tools live recompilation demo I was able to live edit this fragment using breakpoints, live-edit, ctrl+s or cmd+s, and play, with a successful result.
;(function () {
var id = 'rest'; // should be 'result' instead
var code = ['var result = document.getElementById(\'' + id + '\');',
'result.textContent = \'success\';'
];
Function(code.join('\n'))();
}());
So, yes, it can be done, as our tools are maturing.
performance
It depends. Not all JavaScript runtimes optimize the same things, or even in the same way. OK, truism, yes. Everyone immersed in the performance wars has learned that performance varies, and not all things require performance optimization.
Nicholas Zakas in
High Performance JavaScript,
illustrates the performance cost that use of eval
or Function
incurs.
Here's the relevant excerpt
with a data table showing the cost in time for each browser runtime.
Published in 2010, that table shows performance slowing by whole orders of magnitude (10 to 100 times) depending which runtime is used.
Since then, the browser engine wars have narrowed this difference significantly.
From this jsperf test,
comparing eval
, Function()
and function expression
, the only consistent
results I've seen on so far:
Function
is consistently slowest on FF 27 by 60% or more ~ this is the worst disparity among the three approaches on any of the modern browsers I've tried.- no strategy wins on IE 11 ~ each approach has shown up as 'fastest' on repeated test runs
- performance distribution is narrowest on Chrome 33 and Opera 19
(BTW, Don't abuse JSPerf ~ thanks ;)
projects
Initially I examined these cases after looking through a couple of interesting projects for node.js.
- load ~ a "paranoid sandbox" that executes browser-like JavaScript on node.js without the module pattern, and removing global assignments after use.
- Testing Private State and Mocking Dependencies
~ interesting example using node.js
vm
module as a code sandbox for mocking during tests.
You can also visit some projects I have done using the Function
constructor in
various ways:
- vm-shim ~ create a code sandbox similar
to node.js
vm.runInContext
,vm.runInNewContext
, andvm.runInThisContext
methods, using the "paranoid sandbox" technique. - metafunction ~ introspection module for mocking and testing a function's internals, also uses the "paranoid sandbox" technique.
- where.js ~ test library helper method for running data-driven tests against a commented text data table directly in the test.