oleedd
oleedd

Reputation: 446

Loss of scope in setTimeout with a string

Why does it give an error?

!function() {
    var a = 3;
    setTimeout('console.log(a)');
}()

It works if to use a function instead of the string.

It also works with eval:

!function() {
    var a = 3;
    eval('console.log(a)');
}()

This question is about theory. No need to fix it.

The problem is with the scope. I don't understand why the scope is lost. Better - some sentences from a source (for example Mozilla site) where it is explained.

Upvotes: 1

Views: 170

Answers (1)

t.niese
t.niese

Reputation: 40852

Because you can imagine the setTimeout function as something like that

function setTimeout(callbackOrString, time) {
    if( typeof callbackOrString === 'string' ) {
        callbackOrString = eval(callbackOrString)
    }
    // pass callbackOrString to event queue 
}

The evaluation of the string happens inside of the setTimeout function, and not at the point when you pass it to the function. So at the point when the string is evaluated, there is no a in that scope.

And eval is not a regular function but more instruction for the engine, and its behaviour is defined here ECMAScript: 18.2.1 eval ( x )

setTimeout is not part of the language specification but part of an API, and just a "regular" function Living Standard: timer initialization steps (see 1. and 3.):

1. Let method context proxy be method context if that is a WorkerGlobalScope object, or else the WindowProxy that corresponds to method context.  
…  
7. Let task be a task that runs the following substeps:
   …
   2. Run the appropriate set of steps from the following list:
      - If the first method argument is a Function
        …
      - Otherwise
        …
        3. Let settings object be method context's environment settings object.

It is just as if you would write your own function, which results in the same error you get with setTimeout for the exact same reason:

function myFunc(code) {
  if (typeof code === 'string') {
    eval(code)
  } else if (typeof code === 'function') {
    code();
  }
}


!function() {
  var a = 3;

  // arrow function is passed to myFunc, closure is create so "a" is available
  myFunc(() => console.log(a)); 
  
  // function is passed to myFunc, closure is create so "a" is available
  myFunc(function() {
    console.log(a)
  });

  // the string "() => console.log(a)" is evaluated in the scope of "a" and the resulting arrow function is passed to myFunc, closure is create so "a" is available
  myFunc(eval('() => console.log(a)'));

  try {
    // does not work because only the string is passed to myFunc which is evaluated within myFunc
    myFunc('console.log(a)');
  } catch (err) {
    console.error(err);
  }
}()

Upvotes: 2

Related Questions