有没有一种方法可以获取JavaScript中当前范围内的所有变量?
虽然每个人都回答“No”,我知道“不”是正确的答案,但如果你真的需要获取一个函数的本地变量,有一种受限制的方法。
考虑这个函数:
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 访问值。
“在作用域内”的变量是由“作用域链”决定的,这些变量无法以编程方式访问。
有关详细信息(相当多),请查看ECMAScript(JavaScript)规范。这里是官方页面的链接,您可以下载规范的规范版本(PDF),这里是官方的可链接HTML版本。
根据您对Camsoft的评论更新
您的事件函数中的“作用域内”变量是由定义事件函数的位置而不是调用它的方式确定的。但是,您可能会发现通过执行KennyTM指出的类似于for (var propName in ____)
的操作,了解有关this
和参数提供的有用信息,因为这将告诉您在提供给您的各种对象上可用的内容(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的dev tools(即使你平时不使用Chrome进行开发),或者Firebug(即使你平时不使用Firefox进行开发),或者在Opera上使用Dragonfly,或者在IE上使用"F12 Developer Tools"。并且阅读他们提供给你的任何JavaScript文件。并且要用正确的文档敲打它们头部。
with
语句中来实现。请注意,这需要非严格模式,并且这是一种不良习惯。
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);
代理声明拥有with
内引用的所有标识符,因此变量赋值存储在目标中。对于查找,代理从代理目标或全局对象检索值(而不是父作用域)。let
和const
变量不包括在内。
是和不是。在几乎所有情况下都是“不”。但是,如果您想要检查全局范围,那么只有在有限的情况下才会是“是”。以以下示例为例:
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
因此,可以列出当前作用域中的一些变量,但是这种方式不可靠,不简洁,效率低且不易访问。
一个更好的问题是为什么你想知道哪些变量在作用域内?
如果你讨厌你的 CPU,你可以通过暴力破解每个有效的变量名,并对每个变量名进行 eval 操作来查看其结果!
以下代码片段尝试了前1000个暴力破解字符串,足以找到作用域中编造的变量名:
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
nifty
和swell
。请注意,它只会找到由字母组成的变量名。
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 ignoreRegex = /^(?:t(?:his|op)|self|window)$/;
let results = {};
let batch = 100;
let waitMs = 25;
let count = 0;
let startStr = null;
let t = null;
for (let bruteStr of everyPossibleString()) {
// Some property accesses cause problems within stackoverflow's iframe snippet
// system - we'll bypass these problematic properties
if (ignoreRegex.test(bruteStr)) continue;
if (count === 0) {
t = Date.now();
startStr = bruteStr;
}
try { results[bruteStr] = eval(bruteStr); } catch(err) {}
if (count++ >= batch) {
count = 0;
// Update the html
statusElem.innerHTML = `Did batch of ${batch} from ${startStr} -> ${bruteStr} in ${waitMs.toFixed(0)}ms`;
preElem.innerHTML = JSON.stringify(results, null, 2);
// Optimize `batch` based on how well the CPU is holding up
let dur = (Date.now() - t) + 2; // +2ms for breathing room
let estimatedMaxBatch = batch * (waitMs / dur);
batch = Math.round(batch * 0.8 + estimatedMaxBatch * 0.2);
//
await new Promise(r => setTimeout(r, waitMs));
}
}
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>
with
语句可以用于插入链中的其他对象)。 - T.J. Crowder我制作了一个 fiddle,基本上实现了 iman 提出的上述想法。当您在return ipsum*ipsum - ...
中悬停在第二个 ipsum 上时,它是什么样子。
高亮显示具有不同范围的变量声明(使用不同颜色进行区分)。有红色边框的 lorem
是一个被屏蔽的变量(未在范围内,但如果树中下面的另一个 lorem 不在那里,则在范围内)。
我正在使用 esprima 库解析 JavaScript,并使用 estraverse、escodegen 和 escope(是 esprima 之上的实用程序库)。所有“重活”都由这些库完成(当然,最复杂的是 esprima 自身)。
工作原理
ast = esprima.parse(sourceString, {range: true, sourceType: 'script'});
analysis = escope.analyze(ast);
因此,正确答案实际上不是“否”,而是“是,但是”。其中的“但是”非常重要:你基本上需要使用JavaScript重新编写Chrome浏览器(和它的开发工具)。JavaScript是一个图灵完备的语言,所以原则上这是可能的。不可能的是在不使用源代码(作为字符串)的全部内容并且执行高度复杂的操作情况下完成整个过程。
注意:您需要使用未压缩的JS来执行此操作。
现在,您将看到一个对象树,您可以展开其中包含的所有已声明对象。
正如大家所观察到的那样:你不能这样做。 但是,你可以创建一个对象,并将声明的每个变量都分配给该对象。 这样,你就可以轻松地检查你的变量:
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
console.log(window);
查看。http://jsfiddle.net/4x3Tx/ - Stanolet a = v.a = [ 1, 2, 3 ];
- Gershom Maes如果你只是想手动检查变量以帮助调试,只需启动调试器:
debugger;
直接进入浏览器控制台。
this
、arguments
、参数以及所有定义在封闭作用域中的变量。 - Tim Down