Search code examples
javascriptbrowserconstantsvariable-declarationwindow-object

window[name] equivalent to dynamically access const and let declarations


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).


Solution

  • 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.