7

Why can't you access scoped variables using eval under a with statement?

For example:

(function (obj) { 
   with (obj) {
      console.log(a); // prints out obj.a
      eval("console.log(a)"); // ReferenceError: a is not defined
   }
})({ a: "hello" })

EDIT: As the knowledgeable CMS pointed out, this appears to be a browser bug (browsers that use the WebKit console).

If anyone was wondering what abomination I was trying to come up with that would require both the "evil" eval and with -- I was trying to see if I could get a function (used as a callback) executed in another context rather than the one it was defined in. And no, I probably (cough) won't use this anywhere.. more curious than anything.

(function (context,fn) { 
    with (context) 
       eval("("+fn+")()"); 
})({ a: "hello there" }, function () { console.log(a); })
Cristian Sanchez
  • 31,171
  • 11
  • 57
  • 63
  • In which browser are you getting this behavior? Are you running the code on some console? – Christian C. Salvadó Aug 07 '10 at 19:15
  • @CMS: Chrome 5.0.375.125 beta using the built in developer console. Edit: I just tried this with Firefox (firebug) and it worked as expected. Must be a browser bug like you said. – Cristian Sanchez Aug 07 '10 at 19:19
  • @Daniel - It works correctly in Chrome 6.0.472.22 if that helps any – Nick Craver Aug 07 '10 at 19:24
  • @Nick, @Daniel, the problem is present only in the console I get the same behavior on Chrome 6.0.472.25, I'm pretty sure that this issue is somehow related to the WebKit console, because it is also reproducible on Safari 5.0.1 and in a WebKit Nightly build – Christian C. Salvadó Aug 07 '10 at 19:31
  • It doesn't seem to be limited to the `with` statement either. The simple case: `(function (a) { eval("console.log(a)"); })("hello")` also fails. I guess I'll submit the issue unless anyone else wants to. – Cristian Sanchez Aug 07 '10 at 19:37
  • @CMS - You should add this below, will get a +1 from me – Nick Craver Aug 07 '10 at 19:45
  • Both examples work for me with Chrome 5.0.375.55. `eval` and `with`: two unpleasant tastes that taste awful together! – bobince Aug 07 '10 at 19:53
  • Works fine in Chrome 5.0.375.125 beta – David Titarenco Aug 07 '10 at 19:55
  • @bobince, @David, try it on the console, I the `ReferenceError` with Chrome 5.0.375 also – Christian C. Salvadó Aug 07 '10 at 19:59
  • `function testfunc(a) { eval("alert(a)"); } testfunc("hello");` works fine in the Chrome console AND the firebug console, `(function (a) { eval("alert(a)"); })("hello")` only works in the Firebug console (most likely because of calling context) – David Titarenco Aug 07 '10 at 20:05
  • When wrapped in proper global context, both work fine in Chrome/Firefox/Safari... even IE ;) http://polyfx.com/jstest.html – David Titarenco Aug 07 '10 at 20:12
  • Weird... `eval` code doesn't seem to see the argument variables in a function, when the function has been defined in a `FunctionExpression` in the console! Functions defined by `FunctionDeclaration` or defined in the document or a `javascript:` URL are not affected, even if then executed from the console. The version with `with` still works for me even in the console, I can only get it to go wrong for arguments. Weird bug. – bobince Aug 07 '10 at 20:22
  • @Nick, thanks, I left a response to use it as reference for filing the bug. – Christian C. Salvadó Aug 07 '10 at 20:35

4 Answers4

6

This is a bug reproducible only from the WebKit's Console, it has problems binding the caller context when eval is invoked from a FunctionExpression.

When a direct call of eval is made, the evaluated code as you expect should share both the variable environment:

(function (arg) {
  return eval('arg');
})('foo');
// should return 'foo', throws a ReferenceError from the WebKit console

And also the lexical environment:

(function () {
  eval('var localVar = "test"');
})();

typeof localVar; // should be 'undefined', returns 'string' on the Console

In the above function localVar should be declared on the lexical environment of the caller, not on the global context.

For FunctionDeclarations the behavior is completely normal, if we try:

function test1(arg) {
  return eval('arg');
}
test1('foo'); // properly returns 'foo' on the WebKit console

And

function test2() {
  eval('var localVarTest = "test"');
}
test2();
typeof localVarTest; // correctly returns 'undefined'

I have been able to reproduce the problem on the following browsers running on Windows Vista SP2:

  • Chrome 5.0.375.125
  • Chrome 6.0.472.25 dev
  • Safari 5.0.1
  • WebKit Nightly Build r64893
Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
1
(function (obj) {
   with (obj) {
      alert(a); // prints out obj.a
      eval("alert(a)"); // ReferenceError: a is not defined
   }
})({ a: "hello from a with eval" })

function testfunc(a) { eval("alert(a)"); } testfunc("hello from a testfunc eval");

(function (a) { eval("alert(a)"); })("hello from a function constructor eval")

All work fine: http://polyfx.com/jstest.html in FF/Chrome/Safari/IE.

The problem with running snippets of code from various consoles is that the consoles usually screw with the context. (i.e. the Chrome console doesn't appear to be properly wrapping stuff in the global context, whereas the Firebug console does). It could be a bug or (more likely) it could be working as intended.

David Titarenco
  • 32,662
  • 13
  • 66
  • 111
0

Eval always runs in global scope, doesn't it?

Eric
  • 95,302
  • 53
  • 242
  • 374
  • No, a direct call to `eval` will use the calling context (both, the caller lexical and variable environment), an indirect call to eval in ECMAScript 5, e.g.: `var foo = eval; foo('code');` will use the global context, as well the Function constructor. – Christian C. Salvadó Aug 07 '10 at 19:50
0

Putting eval and with aside, new bowsers include the ecma5 Function.prototype.bind method to call a function in some selected object's scope.

For older browsers you can fake it-

Function.prototype.bind= Function.prototype.bind || function bind(scope){
    var method= this;
    return function(){
        method.apply(scope, arguments);
    }
}
kennebec
  • 102,654
  • 32
  • 106
  • 127
  • Note that the *fallback function* is not standards compliant, it can't pre-fill or *curry* a function with known arguments. [This implementation](http://stackoverflow.com/questions/2025789/preserving-a-reference-to-this-in-javascript-prototype-functions/2025839#2025839) is the closest you can get to comply with the ES5 spec., running on an ES3 engine. Also *binding* a function will not give access to the caller's variable or lexical environment (which seems that the OP wants at the end), it can only ensure that the `this` value (and *curried* arguments) will be persisted. – Christian C. Salvadó Aug 08 '10 at 01:23