window[name] 相当于动态访问 const 和 let 声明

Posted

技术标签:

【中文标题】window[name] 相当于动态访问 const 和 let 声明【英文标题】:window[name] equivalent to dynamically access const and let declarations 【发布时间】:2018-10-11 04:18:48 【问题描述】:

闭包之外的旧式 javascript var 声明是全局的(***范围),可以在浏览器中从 window 对象访问。例如,声明 var x = 3; 可以通过 window['x'] 访问。

在给定声明名称(字符串)的情况下,您如何类似地访问 constlet 声明?

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

对于上面的例子,你如何得到 721 的“y”和“z”而不是 undefined

修改代码:https://jsfiddle.net/g78ah6we/

编辑(为清楚起见添加了注释): 1. 虽然不典型,但在某些用例中,例如在 library 中,有必要仅根据声明的名称访问声明。 2. 只需要读取权限(不会修改任何声明)。 3. 提到window 对象只是为了展示旧方法,但这个问题实际上并不是关于使用window 对象(或global 对象)。

【问题讨论】:

Do let statements create properties on the global object?的可能重复 @CertainPerformance 这个问题是关于它们是否在全局对象上,我们已经知道它们不在。这个问题是关于如何解决这个问题的。 它们不在全局对象上,因此无法通过window.ywindow.z 访问它们——这对我来说似乎是同一个问题? 看来这个问题的唯一正确答案将涉及eval,但所谓的 duplicate 甚至没有提到eval(在问题或任何答案)。很明显有些人认为这个问题无法回答。 【参考方案1】:

间接调用eval

可以通过间接调用eval 来访问全局constlet 定义。即使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 将 letconst 变量添加到全局范围:它们是块级声明,代码 eval 评估被视为一个块 - 所以声明当(间接调用)eval 返回时被丢弃。

附言。这是一个技术答案。是的,我以前听说过“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 的间接调用的替代方法。然而,区别主要是语义上的,而不是一个比另一个更好。

问题

如果全局变量名称(varletconstclass 标识符)来自用户输入,则确实应该检查其有效性(not all that easy)或至少在 try/ 中访问catch 块用于在初始化之前捕获使用未声明的标识符或使用名称声明。

我个人建议通常寻找替代使用全局变量名称字符串的方法。想到在库上提供静态名称空间对象(例如myLibrary.data)并处理作为对象属性名称的字符串值,或者在库调用中包含选项对象参数。

【讨论】:

没错,"eval is evil",但它很可能是唯一的解决方案。与直接使用eval('x') 相比,间接使用(0,eval)('x') 的优势仅仅是为了避免范围混淆吗? jsfiddle.net/g78ah6we/3 是的 - 请参阅更新的答案以获得更全面的讨论。 "recommend find alternatives" 该库已经有一个对register a context 的 API 调用(与 WebPack 一起使用时需要),但迫使开发人员使用难以解释的 API 调用只是为了尝试图书馆是不可取的。【参考方案2】:

letconst 都是块作用域。

相比之下,没有var 关键字的变量声明在最外层的函数作用域气泡中创建变量。在浏览器中,最外层的功能范围由window 对象控制。

window 对象不控制的是最外层的块作用域。

如果您的代码无法访问window[nn] 模式中的变量而无法运行,则肯定存在设计问题。

【讨论】:

there definitely is a design issue in it... 如果这都是您自己的代码。另一方面,如果库从声明回调的 DOM 元素中读取属性,您会强制库用户注册(绑定)他们的函数吗?【参考方案3】:

我已将 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' 或任何适合您需要的内容(为简单起见,我在原始问题中使用了带有数字的示例,但我的实际用例实际上是在寻找对象)。

拨弄代码:https://jsfiddle.net/g78ah6we/6/

【讨论】:

以上是关于window[name] 相当于动态访问 const 和 let 声明的主要内容,如果未能解决你的问题,请参考以下文章

框架元素-跨域访问-window.name

跨域访问方法介绍--使用 window.name 传值

JavaScript基础 window.open(url,name,options) 没有菜单栏,没有工具栏

js动态添加对象

JavaScript基础 window.open(url,name,options) 在新打开的窗口中写一些文字

window.history对象