2

If we define a function f() { ... } in eval(...), why isn't it available for the rest of the code, such as #button3's onclick?

As a comparison, if we define the function as f = () => { ... };, then it is available for #button3's onclick.

Why this difference, which seems a little bit contradictory with var functionName = function() {} vs function functionName() {} (here the idea is function f() { ... } has a wider scope than f = () => { ... };).

Click 1 and 3 (Uncaught ReferenceError: f is not defined), and then 2 and 3 (working):

document.getElementById('button1').onclick = () => eval("function f() { alert('hello'); }");
document.getElementById('button2').onclick = () => eval("f = () => { alert('hello'); };");
<div id="button1">1 Click me to define f as a function</div>
<div id="button2">2 Click me to define f as a variable function</div>
<div id="button3" onclick="f();">3 Click to run f()</div>
Basj
  • 41,386
  • 99
  • 383
  • 673

1 Answers1

1

As MDN says on eval:

var-declared variables and function declarations would go into the surrounding scope if the source string is not interpreted in strict mode — for indirect eval, they become global variables. If it's a direct eval in a strict mode context, or if the eval source string itself is in strict mode, then var and function declarations do not "leak" into the surrounding scope.

Here, you're using direct eval (because you're referencing eval) directly, and that "surrounding scope" is the inside of the click handler. To see this working, click, then see how the function f approach defines an f referenceable later in the click handler (but not outside of it):

document.getElementById('button1').onclick = () => {
  eval("function f() { alert('hello'); }");
  f();
}

// scope doesn't extend to out here
setTimeout(() => console.log(typeof f), 3000);
<div id="button1">1 Click me to define f as a function</div>

Doing

f = () => { ... };

is just like doing that in plain code, without eval - without declaring it with const, let, var, or function, you're implicitly assigning to the global object, so

eval("f = () => { alert('hello'); };");

is equivalent to

eval("window.f = () => { alert('hello'); };");

if no f variable exists in scope at that time.

// runs
(() => {
  f = () => console.log('f');
})();
f();


// doesn't run
(() => {
  function g() {
    console.log('g');
  }
})();
g();

Inline handlers may only reference global variables (99% of the time), so for onclick="f();" to work, window.f must exist.

You're using direct eval. Note that if you used indirect eval, the evaled code won't run in the scope of where the eval reference was called, but rather on the top level - and as a result, any variable declaration inside such an eval will be visible everywhere.

// both work
const indirectEval = eval;
document.getElementById('button1').onclick = () => indirectEval("function f() { alert('hello'); }");
document.getElementById('button2').onclick = () => indirectEval("f = () => { alert('hello'); };");
<div id="button1">1 Click me to define f as a function</div>
<div id="button2">2 Click me to define f as a variable function</div>
<div id="button3" onclick="f();">3 Click to run f()</div>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Oh nice! Is it true in general that `f = "anything";` without let, const, var, function is equivalent to `window.f = "anything";`? Is there a canonical SO question/answer about this? – Basj Aug 02 '22 at 22:07
  • In sloppy mode, if `f` hasn't been defined yet, yes - it'll be added as a property to the global object. In strict mode (which is always recommended), trying to assign to a variable that hasn't been declared will instead throw an error. See https://stackoverflow.com/q/1470488 – CertainPerformance Aug 02 '22 at 22:08
  • (...and PS: modules are always treated as Strict Mode.) @Basj – Roko C. Buljan Aug 02 '22 at 22:12
  • Thanks @CertainPerformance! So if we want a `
    ` and the `
    – Basj Aug 02 '22 at 22:15
  • @Basj Yes, inline handlers may only reference global variables (leaving out the silly `with` scope they use that isn't worth thinking about). But this is for informative purposes only. If you're asking this in context of a real app, I'm quite sure there's a much, much better way to structure it, without relying on `eval` nor on inline handlers, both of which are pretty universally considered to be quite bad ideas. – CertainPerformance Aug 02 '22 at 22:16
  • @CertainPerformance Yes, I know about these 2 usual facts (about eval and inline handlers). But I was curious about the limit of what we can do with them. More generally, how would you execute a script that is inserted in the DOM after an AJAX call and HTML fragment insertion? You wouldn't do this https://stackoverflow.com/questions/1197575/can-scripts-be-inserted-with-innerhtml/1197585#1197585? – Basj Aug 02 '22 at 22:19
  • 2
    @Basj I'd extract the text content inside the script tag, then [insert another script tag](https://stackoverflow.com/a/58804758) – CertainPerformance Aug 02 '22 at 22:23