闭包之外的旧式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
对于上面的示例,如何为“y”和“z”而不是7
获取值21
和undefined
?
摆弄代码:
https://jsfiddle.net/g78ah6we/
编辑(为清晰起见添加了注释):
1.虽然不典型,但有些用例,例如来自library内的用例,只需要根据声明的名称访问声明。
2.只需要读取访问权限(不会修改任何声明)
3.提到window
对象只是为了显示旧方法,但这个问题实际上并不是关于使用window
对象(或global
对象)。
答案 0 :(得分:1)
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);
您不能做的是使用间接调用eval将let
或const
变量添加到全局范围:它们是块级声明,并且代码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
标识符)来自用户输入,则应该检查其是否有效({{3}或者至少在try / catch块中访问,以便在初始化之前捕获未使用的标识符或使用名称声明。
就个人而言,我建议一般寻找使用全局变量名字符串的替代方法。在库中提供静态名称空间对象(例如myLibrary.data
)并处理作为对象属性名称的字符串值,或者包括库调用中的选项对象参数,我想到了。
答案 1 :(得分:0)
let
和const
都是块范围的。
相反,没有var
关键字的变量声明会在最外层的功能范围气泡中创建变量。在浏览器中,最外面的功能范围由window
对象控制。
window
对象无法控制的是最外面的块范围。
如果您的代码无法访问window[nn]
模式中的变量,那么肯定存在设计问题。
答案 2 :(得分:0)
我已将 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
注意:
identifierPattern
正则表达式非常简单(不会处理所有有效字符并在保留字上绊倒......正如traktor53指出的那样,"This is more complicated than you might think")。'number'
更改为'object'
或任何适合您需求的内容(为了简单起见,我使用带数字的示例,但我的实际用例实际上是查找对象)。