3

I'm trying to build a simple REPL by evaling user-supplied strings. It seems to work for the most part, except for inputs like "function f() {...}", which have no effect on which functions are visible in future evals. After playing around with it for a bit, I can only conclude that I don't understand eval at all. Here's a short snippet demonstrating some mysterious behaviors:

var xeval = eval;

function silly() {}

eval("function good() {}");

function baffleMe() {
    eval("alsoGood = function() {}");
    eval("function notSoGood() {}");
    xeval("function hope() {}");
    xeval("function crushedHope() { silly(); }");
}

baffleMe();

good();         // Okay.
alsoGood();     // Okay.
notSoGood();    // ReferenceError: notSoGood is not defined
hope();         // Why does this even work?
crushedHope();  // ReferenceError: silly is not defined

Could anyone explain these results, please? (Reproducible in both the latest Chrome and Firefox)

[EDIT]

To clarify, the last call fails only when the code is executed in the Javascript console or tools like JSFiddle, but not when embedded in a script tag. The comments on accepted answer contain the explanation for this.

user3026691
  • 497
  • 2
  • 11
  • See difference between `window.eval` (`global.eval` in Node) and `eval` – elclanrs Jan 13 '16 at 15:02
  • closely related: [global.eval is not able to visit variables in the lexical scope](http://stackoverflow.com/q/31459180/710446) – apsillers Jan 13 '16 at 15:04
  • If you're going to make a REPL, you should put the string evals in `try catch` statements, and if the error message is `Unexpected end of input`, then prompt for more code until the `eval` works, or returns a different error. – Patrick Roberts Jan 13 '16 at 16:12

2 Answers2

1

I will try to explain:

Good is evaluated on the global scope:

good();         // Okay.

Alsogood has no var definition, thus is being defined on the global scope:

alsoGood();     // Okay.

NotSoGood is defined inside the function scope, thus does not exist on the global scope:

notSoGood();    // ReferenceError: notSoGood is not defined

Hope is evaluated by a closure reference of eval on the global scope, thus it's evaluated on the global scope:

hope();         // Why does this even work?

CrushedHope is the same as hope, but silly should be defined in this case:

crushedHope();  // ReferenceError: silly is not defined

As mentioned in the comments below, silly is undefined in the question when using JSFiddle and when the code is wrapped with window.onload.

Konstantin Dinev
  • 34,219
  • 14
  • 75
  • 100
  • @user3026691 `crushedHope` doesn't produce the error you describe when I test it in Chrome's and Firefox's consoles. Where are you seeing this error? – apsillers Jan 13 '16 at 15:11
  • 1
    @user3026691 Ah, I see the problem: if your code isn't global (e.g., wrapped inside of a `(function() { ... })()` IIFE, then `silly` isn't global. However, `crushedHope` *is* global, so it can't see the your locally-defined `silly` function inside the IIFE. – apsillers Jan 13 '16 at 15:14
  • It does in JSFiddle and in the Javascript console... 0_o – user3026691 Jan 13 '16 at 15:14
  • 1
    @user3026691 You need to change your fiddle "Load Type" settings from "onLoad" to "No wrap". Defining your `silly` function inside of a `load` event listener function makes it inaccessible to the global-scope `crushedHope` – apsillers Jan 13 '16 at 15:16
1

This is a feature of ECMAScript5. To quote MDN:

If you use the eval function indirectly, by invoking it via a reference other than eval, as of ECMAScript 5 it works at global scope rather than local scope; this means, for instance, that function declarations create global functions, and that the code being evaluated doesn't have access to local variables within the scope where it's being called.

So directly calling eval() creates the function in local scope. (Using the a=something method automatically creates a global.) But when you indirectly call it by xeval() it gets evaluated globally.

Your final result of silly being undefined is dependent on the scope you're evaluating in. If you're in the console, it seems to have a different scope. If you create a test HTML page, it works properly.

Scimonster
  • 32,893
  • 9
  • 77
  • 89