window[name] эквивалентно динамическому доступу к объявлениям const и let

Объявление JavaScript var в старом стиле вне замыкания является глобальным (область верхнего уровня) и может быть доступно в браузере из объекта window. Например, к объявлению var x = 3; можно получить доступ с помощью window['x'].

Как вы аналогичным образом получаете доступ к объявлению const или let по имени (строке) объявления?

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

Как в приведенном выше примере получить значения 7 и 21 для "y" и "z" вместо undefined?

Поиграйтесь с кодом:
https://jsfiddle.net/g78ah6we/

Правки (примечания добавлены для ясности):
1. Хотя это и не типично, но есть варианты использования, например внутри библиотека, что необходимо получить доступ к объявлению, основываясь только на имени объявления.
2. Необходим доступ только для чтения (ни одно из объявлений не будет изменено).
3. Объект window упоминается только для того, чтобы показать старый способ, но на самом деле этот вопрос не касается использования объекта window (или объекта global).


person Dem Pilafian    schedule 01.05.2018    source источник


Ответы (3)


Использование косвенных вызовов eval

Доступ к глобальным определениям const и let можно осуществить с помощью косвенного вызова eval. То есть сделать eval результатом выражения, разделенного запятыми, или сначала присвоить его переменной. Если синтаксический доступ осуществляется не напрямую к встроенной функции eval, это косвенный доступ, а косвенный доступ выполняется в глобальной области видимости.

Вы также можете установить глобальные let переменные, создав скрипт для выполнения операции настройки.

"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);

Чего вы не можете сделать, так это добавить переменную let или const в глобальную область видимости, используя косвенный вызов eval: они являются объявлениями уровня блока, а код, который оценивает eval, считается блоком, поэтому объявления отбрасываются, когда (косвенный вызов) eval возвращается.

PS. Это технический ответ. И да, я уже слышал, что «eval — это зло», один или три раза.


Для доступа только для чтения с использованием жестко закодированных строк имен переменных (для предотвращения вставки кода) вы можете использовать шаблон:

 (0,eval)("identifierString");

как например:

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
}

Косвенные и прямые звонки на eval

Прямой вызов eval получает только те значения глобальных переменных, которые не были затенены в области действия вызова. Это может ограничить выбор имен переменных или того, откуда можно сделать вызов в библиотеке.

Косвенный вызов выполняется в глобальной области и может получать значения глобальных переменных независимо от затенения имен в библиотеке.

Создание нового объекта Function из исходного текста и его вызов могут стать альтернативой использованию косвенного вызова eval на веб-странице. Однако разница в основном семантическая, а не одна лучше другой.

вопросы

Если имя глобальной переменной (var, let, const или идентификатор class) поступает из пользовательского ввода, его действительно следует проверять на достоверность (не все это easy) или, по крайней мере, доступ в блоке try/catch для перехвата использования необъявленных идентификаторов или использования объявлений имен перед инициализацией.

Лично я бы порекомендовал найти альтернативу использованию строк имен глобальных переменных в целом. На ум приходит предоставление статического объекта пространства имен в библиотеке (например, myLibrary.data) и обработка строковых значений, которые являются именами свойств объекта, или включение параметров объекта option в вызовы библиотеки.

person traktor    schedule 01.05.2018
comment
Верно, eval — это зло, но вполне может быть единственным решением. Преимущество косвенного использования (0,eval)('x') по сравнению с прямым использованием eval('x') только для того, чтобы избежать путаницы в области видимости? jsfiddle.net/g78ah6we/3 - person Dem Pilafian; 01.05.2018
comment
Да - см. обновленный ответ для более полного обсуждения. - person traktor; 02.05.2018
comment
рекомендовать найти альтернативы В библиотеке уже есть вызов API для регистрации контекста (требуется при использовании с WebPack), но заставлять разработчиков использовать сложный для объяснения вызов API только для опробования библиотеки было бы нежелательно. - person Dem Pilafian; 05.05.2018

И let, и const имеют блочную область действия.

Напротив, объявления переменных без ключевого слова var создают переменные в самой внешней функциональной области видимости. В браузерах самая внешняя функциональная область управляется объектом window.

Что объект window не контролирует, так это область самого внешнего блока.

Если ваш код не работает без доступа к переменным в шаблоне window[nn], в нем определенно есть проблема с дизайном.

person Charlie    schedule 01.05.2018
comment
there definitely is a design issue in it... если это ваш собственный код. С другой стороны, если библиотека считывает атрибут из элемента DOM, который объявляет обратный вызов, заставите ли вы пользователя библиотеки зарегистрировать (связать) свои функции? - person Dem Pilafian; 01.05.2018

Я отметил ответ traktor53 как принятый и проголосовал за него, потому что он содержит техническую основу решения.

Если кому-то это будет полезно, вот решение, заключенное в функцию, которая предотвращает выполнение кода в объявлении.

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

Примечания:

  1. Имейте в виду, что регулярное выражение identifierPattern очень упрощено (не обрабатывает все допустимые символы и срабатывает на зарезервированных словах... как указал traktor53, "This сложнее, чем вы думаете").
  2. Измените 'number' на 'object' или что-то другое, подходящее для ваших нужд (для простоты в исходном вопросе я использовал примеры с числами, но мой реальный вариант использования фактически ищет объекты).

Поиграйтесь с кодом:
https://jsfiddle.net/g78ah6we/6/ < br> снимок экрана

person Dem Pilafian    schedule 05.05.2018