获取范围内的所有变量

时间:2010-01-12 19:20:54

标签: javascript

有没有办法获取当前在javascript范围内的所有变量?

12 个答案:

答案 0 :(得分:81)

虽然每个人都回答" "我知道"不"是正确的答案,但如果你真的需要得到一个函数的局部变量,那就有限制了。

考虑这个功能:

var f = function() {
    var x = 0;
    console.log(x);
};

您可以将函数转换为字符串:

var s = f + '';

您将获得函数源作为字符串

'function () {\nvar x = 0;\nconsole.log(x);\n}'

现在您可以使用像esprima这样的解析器来解析函数代码并查找局部变量声明。

var s = 'function () {\nvar x = 0;\nconsole.log(x);\n}';
s = s.slice(12); // to remove "function () "
var esprima = require('esprima');
var result = esprima.parse(s);

找到对象:

obj.type == "VariableDeclaration"
结果中的

(我已删除下面的console.log(x)):

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "x"
                    },
                    "init": {
                        "type": "Literal",
                        "value": 0,
                        "raw": "0"
                    }
                }
            ],
            "kind": "var"
        }
    ]
}

我已经在Chrome,Firefox和Node中对此进行了测试。

但是这个方法的问题是你只需要在函数本身中定义变量。例如,对于这个:

var g = function() {
    var y = 0;
    var f = function() {
        var x = 0;
        console.log(x);
    };
}

您只能访问 x 而不是 y 。 但是你仍然可以在循环中使用调用者(arguments.callee.caller.caller.caller)链来查找调用函数的局部变量。如果您拥有所有本地变量名称,那么您有范围变量。使用变量名称,您可以使用简单的eval访问值。

答案 1 :(得分:76)

没有。 “范围内”变量由“范围链”确定,该范围链无法以编程方式访问。

有关详细信息(非常多),请查看ECMAScript(JavaScript)规范。 Here's a link到官方页面,您可以在其中下载规范规范(PDF)和here's one到官方可链接的HTML版本。

根据您对Camsoft的评论进行更新

事件函数的范围中的变量取决于您定义事件函数的位置,而不是它们如何调用它。 但是,您可以通过{KenisTM指出的方式(this)找到有关您的函数可用于for (var propName in ____)和参数的有用信息,因为这将告诉您你可以获得提供给你的各种对象(this和参数;如果你不确定他们给你什么参数,你可以通过为每个函数隐式定义的arguments变量找到。

因此,除了您定义函数的位置之外,您还可以通过其他方式找到其他可用的内容:

var n, arg, name;
alert("typeof this = " + typeof this);
for (name in this) {
    alert("this[" + name + "]=" + this[name]);
}
for (n = 0; n < arguments.length; ++n) {
    arg = arguments[n];
    alert("typeof arguments[" + n + "] = " + typeof arg);
    for (name in arg) {
        alert("arguments[" + n + "][" + name + "]=" + arg[name]);
    }
}

(您可以扩展它以获得更多有用的信息。)

不过,我可能会使用像Chrome开发工具这样的调试器(即使你通常不使用Chrome进行开发)或Firebug(即使你通常不使用Firefox也是如此)开发),或Opera上的Dragonfly,或IE上的“F12开发人员工具”。并阅读他们为您提供的任何JavaScript文件。并且为了正确的文档而击败他们。 : - )

答案 2 :(得分:31)

是和否。几乎在所有情况下都“不”。 “是的,”但是只有在有限的方式,如果你想检查全球范围。请看以下示例:

var a = 1, b = 2, c = 3;

for ( var i in window ) {
    console.log(i, typeof window[i], window[i]);
}

在150多个其他内容中输出,以下内容:

getInterface function getInterface()
i string i // <- there it is!
c number 3
b number 2
a number 1 // <- and another
_firebug object Object firebug=1.4.5 element=div#_firebugConsole
"Firebug command line does not support '$0'"
"Firebug command line does not support '$1'"
_FirebugCommandLine object Object
hasDuplicate boolean false

因此,可以列出当前范围内的一些变量,但不可靠,简洁,高效或易于访问。

更好的问题是为什么你想知道范围内的变量是什么?

答案 3 :(得分:17)

在ECMAScript 6中,通过将代码包含在带有代理对象的with语句中,或多或少可以实现。请注意,它需要非严格模式,这是不好的做法。

&#13;
&#13;
function storeVars(target) {
  return new Proxy(target, {
    has(target, prop) { return true; },
    get(target, prop) { return (prop in target ? target : window)[prop]; }
  });
}
var vars = {}; // Outer variable, not stored.
with(storeVars(vars)) {
  var a = 1;   // Stored in vars
  var b = 2;   // Stored in vars
  (function() {
    var c = 3; // Inner variable, not stored.
  })();
}
console.log(vars);
&#13;
&#13;
&#13;

代理声称拥有with内引用的所有标识符,因此变量分配存储在目标中。对于查找,代理将从代理目标或全局对象(而不是父作用域)中检索值。 <{1}}和let变量不包括在内。

this answer启发Bergi

答案 4 :(得分:16)

你不能。

变量,函数声明的标识符和函数代码的参数被绑定为Variable Object的属性,这是不可访问的。

另见:

答案 5 :(得分:7)

众所周知:你做不到。 但是你可以创建一个obj并将你声明的每个var分配给该obj。 通过这种方式,您可以轻松查看您的变量:

var v = {}; //put everything here

var f = function(a, b){//do something
}; v.f = f; //make's easy to debug
var a = [1,2,3];
v.a = a;
var x = 'x';
v.x = x;  //so on...

console.log(v); //it's all there

答案 6 :(得分:7)

访问特定范围内的变量的最简单方法

  1. 打开开发人员工具&gt;资源(在Chrome中)
  2. 使用可访问该范围的函数打开文件(提示cmd / ctrl + p查找文件)
  3. 在该函数中设置断点并运行代码
  4. 当它在断点处停止时,您可以通过控制台(或范围var窗口)
  5. 访问范围var

    注意:您希望针对未缩小的js执行此操作。

    显示所有非私人变种的最简单方法

    1. 打开控制台(在Chrome中)
    2. 输入: this.window
    3. 按Enter键
    4. 现在,您将看到一个可以使用所有声明的对象进行扩展的对象树。

答案 7 :(得分:5)

您有多少时间?

如果您讨厌CPU,则可以对每个有效的变量名进行暴力破解,然后eval对每个变量名进行暴力破解,以查看其是否产生值!

以下代码段尝试使用前1000个bruteforce字符串,足以在范围内找到人为的变量名称:

let alpha = 'abcdefghijklmnopqrstuvwxyz';
let everyPossibleString = function*() {
  yield '';
  for (let prefix of everyPossibleString()) for (let char of alpha) yield `${prefix}${char}`;
};
let allVarsInScope = (iterations=1000) => {  
  let results = {};
  let count = 0;
  for (let bruteforceString of everyPossibleString()) {
    if (!bruteforceString) continue; // Skip the first empty string
    try { results[bruteforceString] = eval(bruteforceString); } catch(err) {}
    if (count++ > iterations) break;
  }
  return results;
};

let myScope = (() => {
    
  let dd = 'ddd';
  let ee = 'eee';
  let ff = 'fff';
  
  ((gg, hh) => {
    
    // We can't call a separate function, since that function would be outside our
    // scope and wouldn't be able to see any variables - but we can define the
    // function in place (using `eval(allVarsInScope.toString())`), and then call
    // that defined-in-place function
    console.log(eval(allVarsInScope.toString())());
    
  })('ggg', 'hhh');
  
})();

此脚本最终(在很长一段时间后)将找到所有范围变量名称,以及abc niftyswell,一些示例变量我建立。请注意,它只会找到由字母字符组成的变量名。

let preElem = document.getElementsByClassName('display')[0];
let statusElem = document.getElementsByClassName('status')[0];
let alpha = 'abcdefghijklmnopqrstuvwxyz';
alpha += alpha.toUpperCase();
let everyPossibleString = function*() {
  yield '';
  for (let prefix of everyPossibleString()) for (let char of alpha) yield `${prefix}${char}`;
};

(async () => {
  
  let abc = 'This is the ABC variable :-|';
  let neato = 'This is the NEATO variable :-)';
  let swell = 'This is the SWELL variable :-D';
  
  let results = {};
  let batch = 25000;
  let waitMs = 25;
  let count = 0;
  let startStr = null;

  for (let bruteStr of everyPossibleString()) {
    
    try {

      if (bruteStr === '') continue;
      if (startStr === null) startStr = bruteStr;

      try { results[bruteStr] = eval(bruteStr); } catch(err) {}

      if (count++ >= batch) {
        
        statusElem.innerHTML = `Did batch of ${batch} from ${startStr} -> ${bruteStr}`;
        preElem.innerHTML = JSON.stringify(results, null, 2);
        
        count = 0;
        startStr = null;
        await new Promise(r => setTimeout(r, waitMs));

      }
      
    } catch(err) {
      
      // It turns out some global variables are protected by stackoverflow's snippet
      // system (these include "top", "self", and "this"). If these values are touched
      // they result in a weird iframe error, captured in this `catch` statement. The
      // program can recover by replacing the most recent `result` value (this will be
      // the value which causes the error).
      let lastEntry = Object.entries(results).slice(-1)[0];
      results[lastEntry[0]] = '<a protected value>';
            
    }

  }
  
  console.log('Done...'); // Will literally never happen

})();
html, body { position: fixed; left: 0; top: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden }
.display {
  position: fixed;
  box-sizing: border-box;
  left: 0; top: 0;
  bottom: 30px; right: 0;
  overflow-y: scroll;
  white-space: pre;
  font-family: monospace;
  padding: 10px;
  box-shadow: inset 0 0 10px 1px rgba(0, 0, 0, 0.3);
}
.status {
  position: fixed;
  box-sizing: border-box;
  left: 0; bottom: 0px; right: 0; height: 30px; line-height: 30px;
  padding: 0 10px;
  background-color: rgba(0, 0, 0, 1);
  color: rgba(255, 255, 255, 1);
  font-family: monospace;
}
<div class="display"></div>
<div class="status"></div>

我太清楚了,几乎没有任何可行的方法

答案 8 :(得分:3)

如果您只是想手动检查变量以帮助调试,只需启动调试器:

debugger;

直接进入浏览器控制台。

答案 9 :(得分:0)

我做了fiddle(基本上)实现了iman概述的上述想法。将鼠标悬停在return ipsum*ipsum - ...

中的第二个ipsum上的样子如下

enter image description here

范围内的变量在声明它们的地方突出显示(不同范围的颜色不同)。带有红色边框的lorem是一个带阴影的变量(不在范围内,但是如果树后面的另一个lorem不存在,则在范围内。)

我正在使用esprima库来解析JavaScript,estraverse,escodegen,escope(esprima之上的实用程序库)。“繁重的工作”全部由这些库完成(最复杂的当然是esprima本身) )

工作原理

ast = esprima.parse(sourceString, {range: true, sourceType: 'script'});

生成抽象语法树。然后,

analysis = escope.analyze(ast);

生成一个复杂的数据结构,其中封装了有关程序中所有作用域的信息。其余人员将收集在该分析对象(和抽象语法树本身)中编码的信息,并从中制定出交互式的着色方案。

因此,正确的答案实际上不是“否”,而是“是,而是”。 “但是”是一个很大的问题:您基本上必须用JavaScript重写chrome浏览器的重要部分(它是devtools)。 JavaScript是Turing的完整语言,因此原则上当然是可能的。不可能在不使用整个源代码(作为字符串)的情况下完成整个操作,然后使用它来执行非常复杂的事情。

答案 10 :(得分:0)

您可以在 [[Scopes]] 中查看作用域及其变量,甚至可以使用 console.dir() 来查看闭包作用域。

示例 1:

counterWithClosure = (function () {
    let internalVar = 0
    return function () {
        return ++internalVar
    }
})()

counterWithClosure() // 1
counterWithClosure() // 2
counterWithClosure() // 3

console.dir(counterWithClosure)

如果页面有可访问的脚本,它会显示“[[Scopes]] > Closure”、“[[Scopes]] > Global”甚至“[[Scopes]] > Script”中的变量。

带输出的图像: console.dir output

即使使用嵌套闭包,您也可以看到嵌套作用域。

示例 2:

adderWithNestedClosures = (function () {
    let varLevel1 = 1
    return function (param1) {
        let varLevel2 = 10
        return function (param2) {
            let varLevel3 = 100
            return function (param3) {
                let varLevel4 = 1000
                return function (param4) {
                    ++varLevel1
                    ++varLevel2
                    ++varLevel3
                    ++varLevel4
                    return {
                        paramsSum: param1 + param2 + param3 + param4,
                        varsSum: varLevel1 + varLevel2 + varLevel3 + varLevel4
                    }
                }
            }
        }
    }
})()

adderWith123 = adderWithNestedClosures(1)(2)(3) // Preparing function with nested scopes
adderWith123(4) // {paramsSum:10,varsSum:1115}
adderWith123(4) // {paramsSum:10,varsSum:1119}
adderWith123(5) // {paramsSum:11,varsSum:1123}
console.dir(adderWith123) 

它显示了 [[Scopes]] 中的所有嵌套作用域 带输出的图像: console.dir with nested scopes

它适用于基于 Chromium 的浏览器。

答案 11 :(得分:-3)

  1. 从一开始就获得任何人以任何方式或方式编写或输入的每个单词的列表(绝对是数学意义上的有限列表。)

  2. 将它们全部输入到一个大数组中,再将它们全部输入到您的开发控制台中(作为字符串,这样就不会在此处抛出错误。)

  3. 创建新数组,并在有限列表上执行循环,如果try / catch(在try中执行此操作)未在catch中结束,则由于ReferenceError(使用eval)而推入新数组“unstring”,因为你确实想要这里的错误,如果不在范围内)。

  4. 我把它拿回来。我在第1点谈到的清单还不够大。他们可以通过window [randomGeneratedString] = something创建变量。更好地循环所有字符串,这些字符串可以由任何计算机程序在合理的可行时间范围内停止生成 - 例如,自计算机发明以来的时间。

  5. 好吧,说真的,你可以这样做,使用1)你在整个代码库上运行esprima.parse得到的东西,然后在树上找到名为“Identifier”的东西并存储它们的“名字”。但是你会错过正在窗口创建的变量[“file_”+ i] = blah,见4。