Skip to content
Advertisement

Eval a function definition vs. variable function definition

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>

Advertisement

Answer

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>
Advertisement