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

て烟熏妆下的殇ゞ 提交于 2019-12-06 16:00:59

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.

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.

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/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!