One of the weirdest aspects of JavaScript's
eval is that it not only has complete access to the lexical scope of its caller, but it even gets to add variable declarations to its caller's lexical frame. So I can write
(function() {
eval("var x = 10")
return x
})()
and get 10. Now, bearing in mind that
var declares a binding for the entire containing function,
eval combines in a truly spectacular way with lexical bindings. What does the following
test function produce?
function test() {
var x = 'outer'
return (function() {
{
let x = 'inner'
eval("var x = 'eval'")
}
return x
})()
}
Clearly the
let-binding of
x seems to be in scope for the
eval code. So we might expect the function to return the string
'outer', with the assumption that the
eval code simply modified the inner binding. Then again, the
eval code is creating a
var-binding, so we might expect the function to return the string
'eval', with the assumption that the
eval code is declaring a new variable
x to be local to the inner function. So what does it really return?
undefined.
What?If I've got this right, what's happening is that the
eval code is creating a
var-binding for
x in its containing function, but its initializer is mutating the current
x in scope, which happens to be the
let-bound x. So the
eval code is actually creating a variable binding
two lexical frames out. The
evaled
var-declaration and its initializer are referring to two completely different bindings of
x.
Lest you think this is strictly an unfortunate interaction between the legacy
var and
eval semantics and the new (post-ES3)
let declaration form, there are a couple of other local declaration forms in pure ES3, including
catch. Consider:
function test() {
var x = 'outer'
return (function() {
try { throw 'inner' }
catch(x) { eval("var x = 'eval'") }
return x
})()
}