loading...

JavaScript – Evaluation Expressions

Like many interpreted languages, JavaScript has the ability to
interpret strings of JavaScript source code, evaluating them to
produce a value. JavaScript does this with the global function
eval():

eval("3+2")    // => 5

Dynamic evaluation of strings of source code is a powerful
language feature that is almost never necessary in practice. If you
find yourself using eval(), you
should think carefully about whether you really need to use it.

The subsections below explain the basic use of eval() and then explain two restricted
versions of it that have less impact on the optimizer.

Is eval() a Function or an Operator?

eval() is a function, but
it is included in this chapter on expressions because it really
should have been an operator. The earliest versions of the language
defined an eval() function, and
ever since then language designers and interpreter writers have been
placing restrictions on it that make it more and more operator-like.
Modern JavaScript interpreters perform a lot of code analysis and
optimization. The problem with eval() is that the code it evaluates is,
in general, unanalyzable. Generally speaking, if a function calls
eval(), the interpreter cannot
optimize that function. The problem with defining eval() as a function is that it can be
given other names:

var f = eval;
var g = f;

If this is allowed, then the interpreter can’t safely optimize
any function that calls g(). This
issue could have been avoided if eval was an operator (and a reserved
word). We’ll learn below (in Global eval() and Strict eval()) about restrictions placed on eval() to make it more
operator-like.

eval()

eval() expects one
argument. If you pass any value other than a string, it simply
returns that value. If you pass a string, it attempts to parse the
string as JavaScript code, throwing a SyntaxError if it fails. If it
successfully parses the string, then it evaluates the code and
returns the value of the last expression or statement in the string
or undefined if the last
expression or statement had no value. If the evaluated string throws
an exception, that exception propogates from the call to eval().

The key thing about eval()
(when invoked like this) is that it uses the variable environment of
the code that calls it. That is, it looks up the values of variables
and defines new variables and functions in the same way that local
code does. If a function defines a local variable x and then calls eval("x"), it will obtain the value of the
local variable. If it calls eval("x=1"), it changes the value of the
local variable. And if the function calls eval("var y = 3;"), it has declared a new
local variable y. Similarly a
function can declare a local function with code like this:

eval("function f() { return x+1; }");

If you call eval() from
top-level code, it operates on global variables and global
functions, of course.

Note that the string of code you pass to eval() must make syntactic sense on its
own—you cannot use it to paste
code fragments into a function. It makes no sense to write eval("return;"), for example, because
return is only legal within
functions, and the fact that the evaluated string uses the same
variable environment as the calling function does not make it part
of that function. If your string would make sense as a standalone
script (even a very short one like x=0 ), it is legal to pass to eval(). Otherwise eval() will throw a SyntaxError.

Global eval()

It is the ability of eval()
to change local variables that is so problematic to JavaScript
optimizers. As a workaround, however, interpreters simply do less
optimization on any function that calls eval(). But what should a JavaScript
interpreter do, however, if a script defines an alias for eval() and then calls that function by
another name? In order to simplify the job of JavaScript
implementors, the ECMAScript 3 standard declared that interpreters
did not have to allow this. If the eval() function was invoked by any name
other than “eval”, it was allowed to throw an EvalError.

In practice, most implementors did something else. When
invoked by any other name, eval()
would evaluate the string as if it were top-level global code. The
evaluated code might define new global variables or global
functions, and it might set global variables, but it could not use
or modify any variables local to the calling function, and would
not, therefore, interfere with local optimizations.

ECMAScript 5 deprecates EvalError and standardizes the de
facto behavior of eval(). A
“direct eval” is a call to the eval() function with an expression that
uses the exact, unqualified name “eval” (which is beginning to feel
like a reserved word). Direct calls to eval() use the variable environment of the
calling context. Any other call—an indirect call—uses the global object
as its variable environment and cannot read, write, or define local
variables or functions. The following code demonstrates:

var geval = eval;                 // Using another name does a global eval
var x = "global", y = "global";   // Two global variables
function f() {                    // This function does a local eval
    var x = "local";              // Define a local variable
    eval("x += 'changed';");      // Direct eval sets local variable
    return x;                     // Return changed local variable
}
function g() {                    // This function does a global eval
    var y = "local";              // A local variable
    geval("y += 'changed';");     // Indirect eval sets global variable
    return y;                     // Return unchanged local variable
}
console.log(f(), x); // Local variable changed: prints "localchanged global": 
console.log(g(), y); // Global variable changed: prints "local globalchanged":

Notice that the ability to do a global eval is not just an
accommodation to the needs of the optimizer, it is actually a
tremendously useful feature: it allows you to execute strings of
code as if they were independent, top-level scripts. As noted at the
beginning of this section, it is rare to truly need to evaluate a
string of code. But if you do find it necessary, you are more likely
to want to do a global eval than a local eval.

Before IE9, IE differs from other browsers: it does not do a
global eval when eval() is
invoked by a different name. (It doesn’t throw an EvalError either:
it simply does a local eval.) But IE does define a global function
named execScript() that executes
its string argument as if it were a top-level script. (Unlike
eval(), however, execScript() always returns null.)

Strict eval()

ECMAScript 5 strict mode (see “use strict”)
imposes further restrictions on the behavior of the eval() function and even on the use of the
identifier “eval”. When eval() is
called from strict mode code, or when the string of code to be
evaluated itself begins with a “use strict” directive, then eval() does a local eval with a private
variable environment. This means that in strict mode, evaluated code
can query and set local variables, but it cannot define new
variables or functions in the local scope.

Furthermore, strict mode makes eval() even more operator-like by
effectively making “eval” into a reserved word. You are not allowed
to overwrite the eval() function
with a new value. And you are not allowed to declare a variable,
function, function parameter, or catch block parameter with the name
“eval”.

Comments are closed.

loading...