2

The old style JavaScript var declaration outside of a closure is global (top-level scope) and can be accessed in a browser from the window object. For example, the declaration var x = 3; can be accessed with window['x'].

How do you similarly access a const or let declaration given the name (string) of the declaration?

var x = 3;
const y = 7;
let z = 21;

console.log('x = ' + window['x']);  //x = 3
console.log('y = ' + window['y']);  //y = undefined
console.log('z = ' + window['z']);  //z = undefined

For the above example, how do you get the values 7 and 21 for "y" and "z" instead of undefined?

Fiddle with the code:
https://jsfiddle.net/g78ah6we/

Edits (notes added for clarity):
1. While not typical, there are use cases, such as from within a library, that it's necessary to access a declaration based only on the name of the declaration.
2. Only read access is needed (none of the declarations will be modified).
3. The window object is mentioned just to show the old way, but this question is not actually about using the window object (or the global object).

Dem Pilafian
  • 5,625
  • 6
  • 39
  • 67
  • 1
    Possible duplicate of [Do let statements create properties on the global object?](https://stackoverflow.com/questions/28776079/do-let-statements-create-properties-on-the-global-object) – CertainPerformance May 01 '18 at 04:06
  • @CertainPerformance That question is about if they are on the global object, and we already know they are not. This questions is about how to workaround the issue. – Dem Pilafian May 01 '18 at 04:11
  • They're not on the global object, so it's impossible to access them through `window.y` and `window.z` - it seems to be the same issue to me? – CertainPerformance May 01 '18 at 04:13
  • It looks like the only correct answer to this question will involve `eval`, yet the supposed **duplicate** does not even mention `eval` (in the question or any of the answers). It's clear some people thought that this question was unanswerable. – Dem Pilafian May 01 '18 at 19:19

3 Answers3

4

Using indirect calls to eval

Accessing global const and let definitions can be done using an indirect call to eval. That is make eval the result of a comma separated expression or assign it to a variable first. If the syntactic access is not directly to the built-in eval function it's an indirect access, and indirect access executes in global scope.

You can also set global let variables by building script to perform the setting operation.

"use strict";
let myVar =  "global variable myVar";

console.log(  myVar);

(function myLibrary() {

    const myVar = "local variable myVar";

    const indirectEval = eval;
    var varName = "myVar";

    console.log( eval(varName));   // direct call uses local scope
    console.log( indirectEval(varName));  // indirect call uses global scope

    var result = "\"updated global variable even though shadowed\"";
    var js = varName + '=' + result;
    indirectEval(js);

    // but trying to define a new let variable doesn't attach to global scope

    var js2 ='let letVar2 = "let variable two"';
    indirectEval( js2);
})();
console.log( myVar)

console.log( "letVar2: " + typeof letVar2);

What you can't do is add a let or const variable to global scope using an indirect call to eval: they are block level declarations and the code eval evaluates is considered a block - so the declarations are discarded when (indirect call to ) eval returns.

PS. This is a technical answer. And yes, I have heard that "eval is evil" before, one or three times.


For read access only using hard-coded variable name strings (to prevent code insertion) you could use the pattern:
 (0,eval)("identifierString");

as for example:

var x = 3;
const y = 7;
let z = 21;

{
  const y = "shadow"
  let z = 42;

  console.log('x = ' +  (0,eval)('x'));  //x = 3
  console.log('y = ' + (0,eval)('y'));  //y = 7
  console.log('z = ' + (0,eval)('z'));  //z = 21
}

Indirect vs direct calls to eval

A direct call to eval only obtains the values of global variables that have not been shadowed in function scope of the call. This may restrict choice of variable names, or where the call can be made from, within the library.

An indirect call executes in global scope and can obtain the value of global variables irrespective of name shadowing within the library.

Creating a new Function object from source text, and calling it, may provide be an alternative to using an indirect call to eval in a web page. However the difference is largely semantic rather than one being better than the other.

Issues

If the global variable name (var, let, const or class identifier) comes from user input it really should be checked for validity (not all that easy) or at least accessed within a try/catch block to trap used of undeclared identifiers or use of name declarations before initialization.

Personally I would recommend finding alternatives to using global variable name strings in general. Providing a static name space object on the library (e.g. myLibrary.data) and processing string values that are property names of the object, or including option object parameters in library calls, come to mind.

traktor
  • 17,588
  • 4
  • 32
  • 53
  • True, _"eval is evil"_, but it may very well be the only solution. Is the advantage of the indirect use of `(0,eval)('x')` over the direct use of `eval('x')` just to avoid scope confusion? https://jsfiddle.net/g78ah6we/3/ – Dem Pilafian May 01 '18 at 18:59
  • 1
    Yes - see the updated answer for a fuller discussion. – traktor May 02 '18 at 00:16
  • *"recommend finding alternatives"* The library already has an API call to [register a context](http://dnajs.org/docs/#api-register-context) (required when used with WebPack), but forcing developers to use a hard to explain API call just to try out the library would be undesirable. – Dem Pilafian May 05 '18 at 17:56
0

Both let and const are block scoped.

In contrast, the variable declarations without var keyword creates variables in the outermost functional scope bubble. In browsers, the outermost functional scope is controlled by the window object.

What the window object doesn't control is the outermost block scope.

If your code doesn't work without being able to access variables in the window[nn] pattern, there definitely is a design issue in it.

Charlie
  • 22,886
  • 11
  • 59
  • 90
  • `there definitely is a design issue in it`... if it's all your own code. On the other hand, if a library reads an attribute from a DOM element that declares a callback, would you force the library user to register (bind) their functions? – Dem Pilafian May 01 '18 at 05:39
0

I've marked traktor53's answer as accepted and upvoted it because it contains the technical core of the solution.

In case it's helpful for anyone, here's a solution wrapped up into a function that prevents executing code in the declaration.

var x = 3;
const y = 7;
let z = 21;
const malware = 'alert("Game over.");';

function getDeclaration(name) {
   var identifierPattern = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
   var topLevelGet = (null, eval);
   return identifierPattern.test(name) && topLevelGet('typeof ' + name) === 'number' ?
      topLevelGet(name) : null;
   }

console.log(getDeclaration('x'));        //output: 3
console.log(getDeclaration('y'));        //output: 7
console.log(getDeclaration('z'));        //output: 21
console.log(getDeclaration('bogus'));    //output: null
console.log(getDeclaration('malware'));  //output: null
console.log(getDeclaration('if'));       //EXCEPTION: unexpected keyword

Notes:

  1. Be aware that the identifierPattern regex is very simplistic (does not handle all valid characters and trips up on reserved words... as traktor53 pointed out, "This is more complicated than you might think").
  2. Change 'number' to 'object' or whatever is appropriate for your needs (for simplicity in the original question I used examples with numbers, but my real use case actually looks for objects).

Fiddle with the code:
https://jsfiddle.net/g78ah6we/6/
screen shot

Dem Pilafian
  • 5,625
  • 6
  • 39
  • 67