233

Is there a way to get all variables that are currently in scope in javascript?

Justin Johnson
  • 30,978
  • 7
  • 65
  • 89
Ian
  • 24,116
  • 22
  • 58
  • 96
  • Re your answer to Camsoft: That's a totally different question; I've updated my answer to address it. – T.J. Crowder Jan 12 '10 at 19:42
  • 3
    It's just a general question, being more specific wont help much since I'm working with an obscure API with poor documentation. – Ian Jan 12 '10 at 19:43
  • You mean global variables! You can display the *enumerable* global variables using `for (v in this) alert(v);`. Not all globals are enumerable, though, and I know of no standard way to get a list of the non-enumerable ones. – Jason Orendorff Jan 12 '10 at 19:44
  • 2
    @Jason - No, the question is clear. Inside a function the variables in scope will include global variables, `this`, `arguments`, parameters and all variables defined in enclosing scopes. – Tim Down Jan 12 '10 at 20:18
  • 3
    This is why I miss Perl's symbol tables. Any plans to add this to a future release of Javascript? – Dexygen Sep 27 '15 at 11:15
  • YES. here is my fiddle: https://jsfiddle.net/mathheadinclouds/bvx1hpfn/11/ – mathheadinclouds Nov 14 '19 at 15:28
  • If, like me, you came to this question wanting to see all the variables in a Chrome browser debug console, see: https://stackoverflow.com/questions/2934787/view-list-of-all-javascript-variables-in-google-chrome-console – Arthur Hebert-Ryan Mar 07 '21 at 00:38

11 Answers11

134

Although everyone answer "No" and I know that "No" is the right answer but if you really need to get local variables of a function there is a restricted way.

Consider this function:

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

You can convert your function to a string:

var s = f + '';

You will get source of function as a string

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

Now you can use a parser like esprima to parse function code and find local variable declarations.

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

and find objects with:

obj.type == "VariableDeclaration"

in the result (I have removed console.log(x) below):

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

I have tested this in Chrome, Firefox and Node.

But the problem with this method is that you just have the variables defined in the function itself. For example for this one:

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

you just have access to the x and not y. But still you can use chains of caller (arguments.callee.caller.caller.caller) in a loop to find local variables of caller functions. If you have all local variable names so you have scope variables. With the variable names you have access to values with a simple eval.

iman
  • 21,202
  • 8
  • 32
  • 31
  • 14
    Excellent technique here at tackling the original problem. Deserves an upvote. – deepelement Dec 09 '14 at 21:04
  • 4
    A caller's variables are not necessarily scope variables. Also, scope variables aren't necessarily in the call chain. Variables that have been closed over may be referenced by a closure function upon calling it from a caller that is outside of the definition/closure scope (and whose variables are not at all accessible from withing the closure's body). – DDS Feb 01 '15 at 19:51
  • Could you please explain "a simple eval" clear? I tried with below code but could not get variable trapped in scope.```function getCounter(start){ return function(){ start ++; return start; } } var counter = getCounter(1); var startInClosure = eval.call(counter, 'this.start;'); console.log(startInClosure);``` It prints `undefined` while I expect it should be 2. – Pylipala Jun 03 '15 at 02:54
  • @Pylipala The question here is about variables in scope not getting value of a local variables from outside of scope. That's not possible, because this is the definition of local variable that should not be accessible from outside. Regarding your code, you mean context not scope but you didn't put *start* in the context (this), so you can not read it with *this.start*. See here: http://paste.ubuntu.com/11560449/ – iman Jun 04 '15 at 08:09
  • @iman Thanks for your answer. I also guess it is not possible in Javascript side, so I guess if it is possible in v8 side. I found below question [link](http://stackoverflow.com/questions/7061596/get-all-values-of-the-closure-in-node-js-or-v8) which describe my problem accurately. In it Lasse Reichstein says no but mako-taco says yes. I tried some C++ code as [link](http://paste.ubuntu.com/11561159/) but do not know how to write it. – Pylipala Jun 04 '15 at 08:54
  • Such a crafty response. Good find!! – Bryan Grace Sep 06 '16 at 21:57
  • i don't think parsing the code is going to work if the code doesn't explicitly assign the value of the local in a way that you can easily find.. in other words, except for trivial cases it's useless. – Michael Apr 26 '17 at 15:19
  • I implemented this, working fiddle available, see my answer below. @Pacerier: there are several, acorn/babel being the strongest/best. You can compare them here: https://astexplorer.net/ – mathheadinclouds Nov 14 '19 at 15:18
  • @iman: you don't need eval. Just do everything in the before-run time. See my answer below. – mathheadinclouds Nov 14 '19 at 15:20
92

No. "In scope" variables are determined by the "scope chain", which is not accessible programmatically.

For detail (quite a lot of it), check out the ECMAScript (JavaScript) specification. Here's a link to the official page where you can download the canonical spec (a PDF), and here's one to the official, linkable HTML version.

Update based on your comment to Camsoft

The variables in scope for your event function are determined by where you define your event function, not how they call it. But, you may find useful information about what's available to your function via this and arguments by doing something along the lines of what KennyTM pointed out (for (var propName in ____)) since that will tell you what's available on various objects provided to you (this and arguments; if you're not sure what arguments they give you, you can find out via the arguments variable that's implicitly defined for every function).

So in addition to whatever's in-scope because of where you define your function, you can find out what else is available by other means by doing:

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]);
    }
}

(You can expand on that to get more useful information.)

Instead of that, though, I'd probably use a debugger like Chrome's dev tools (even if you don't normally use Chrome for development) or Firebug (even if you don't normally use Firefox for development), or Dragonfly on Opera, or "F12 Developer Tools" on IE. And read through whatever JavaScript files they provide you. And beat them over the head for proper docs. :-)

Daniel Wolf
  • 12,855
  • 13
  • 54
  • 80
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • As a side note, Google Chrome has a great debugger comparable to Firebug (with a bit more icing on top as well). – Swivel Mar 19 '14 at 16:02
  • 4
    @Swivelgames: Oh, I much prefer Chrome's Dev Tools to Firefox+Firebug. They just weren't up to much back in 2010. :-) – T.J. Crowder Mar 19 '14 at 16:06
  • 2
    @tjcrowder Indeed! I noticed the timestamp on the answer was a while ago :) – Swivel Mar 19 '14 at 16:22
47

In ECMAScript 6 it's more or less possible by wrapping the code inside a with statement with a proxy object. Note it requires non-strict mode and it's bad practice.

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

The proxy claims to own all identifiers referenced inside with, so variable assignments are stored in the target. For lookups, the proxy retrieves the value from the proxy target or the global object (not the parent scope). let and const variables are not included.

Inspired by this answer by Bergi.

Klesun
  • 12,280
  • 5
  • 59
  • 52
Oriol
  • 274,082
  • 63
  • 437
  • 513
34

Yes and no. "No" in almost every situation. "Yes," but only in a limited manner, if you want to check the global scope. Take the following example:

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

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

Which outputs, amongst 150+ other things, the following:

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

So it is possible to list some variables in the current scope, but it is not reliable, succinct, efficient, or easily accessible.

A better question is why do you want to know what variables are in scope?

Justin Johnson
  • 30,978
  • 7
  • 65
  • 89
  • 2
    I think the question was more general and not restricted to javascript in the context of a web browser. – Radu Simionescu Jun 19 '12 at 09:21
  • Just change the word "window" for "global" if you're using node... or you can use the word "this" but that's prohibited under "use strict" – Ivan Castellanos Jan 10 '14 at 02:54
  • I added a line checking for hasOwnProperty. For example: `for ( var i in window ) { if (window.hasOwnProperty(i)) { console.log(i, window[i]); }}`. This will at least cut down the inherited properties and your variables will be among only about 50 other properties. – timctran Jun 10 '15 at 20:30
  • 9
    Why shouldn't somebody at least want to check what variables are in scope, during debugging and/or development purposes. – Dexygen Sep 27 '15 at 11:13
  • Another reason to want to know what variables are in local scope is to serialize a closure. – Michael Apr 26 '17 at 15:20
  • @Justin, An even better question is why would the computer not allow us to know what variables are in scope? – Pacerier Sep 13 '17 at 08:33
  • It's not the computer, it's the JavaScript language. A lot of languages don't have that feature. In fact, although there are probably others, the only one I can think of that does is Python. – Justin Johnson Sep 14 '17 at 17:03
  • 1
    Thanks, this may be useful if you need to inspect if some variable exists and how it is defined in an environment that is very complex or was designed by other people. – jcf Jul 11 '19 at 16:10
17

How much time do you have?

If you ḧ̸̬̦́̒͑̌͝ͅa̵͍͖͛t̴̪̟̬̖̾́̋͗̏ͅe̸̳̊̍̈́͌͝ your cpu you can bruteforce through every valid variable name, and eval each one to see if it results in a value!

The following snippet tries the first 1000 bruteforce strings, which is enough to find the contrived variable names in scope:

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');
  
})();

This script will eventually (after a very long time) find all scoped variable names, as well as abc nifty and swell, some example variables I created. Note it will only find variable names consisting of alpha characters.

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>

I am all too aware there is virtually no situation where this is practical

Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
16

You can't.

Variables, identifiers of function declarations and arguments for function code, are bound as properties of the Variable Object, which is not accesible.

See also:

Christian C. Salvadó
  • 807,428
  • 183
  • 922
  • 838
  • 1
    True, but the in-scope variables go beyond just the current variable object. In-scope variables are determined by the *scope chain*, which is a chain consisting (in the normal case) of a series of variable objects and terminating with the global object (although the `with` statement can be used to insert other objects in the chain). – T.J. Crowder Jan 12 '10 at 19:29
  • +1 for links to bclary.com. A HTML version of the up-to-date spec will be appearing in the next couple of months on the ECMA website, I'm glad to say. – T.J. Crowder Jan 12 '10 at 19:30
  • 3
    Just a note, both the links are broken. – 0xc0de Sep 13 '16 at 06:13
  • you CAN - with static analysis https://jsfiddle.net/mathheadinclouds/bvx1hpfn/11/ – mathheadinclouds Nov 14 '19 at 15:31
11

I made a fiddle implementing (essentially) above ideas outlined by iman. Here is how it looks when you mouse over the second ipsum in return ipsum*ipsum - ...

enter image description here

The variables which are in scope are highlighted where they are declared (with different colors for different scopes). The lorem with red border is a shadowed variable (not in scope, but be in scope if the other lorem further down the tree wouldn't be there.)

I'm using esprima library to parse the JavaScript, and estraverse, escodegen, escope (utility libraries on top of esprima.) The 'heavy lifting' is done all by those libraries (the most complex being esprima itself, of course.)

How it works

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

makes the abstract syntax tree. Then,

analysis = escope.analyze(ast);

generates a complex data structure encapsulating information about all the scopes in the program. The rest is gathering together the information encoded in that analysis object (and the abstract syntax tree itself), and making an interactive coloring scheme out of it.

So the correct answer is actually not "no", but "yes, but". The "but" being a big one: you basically have to rewrite significant parts of the chrome browser (and it's devtools) in JavaScript. JavaScript is a Turing complete language, so of course that is possible, in principle. What is impossible is doing the whole thing without using the entirety of your source code (as a string) and then doing highly complex stuff with that.

mathheadinclouds
  • 3,507
  • 2
  • 27
  • 37
9

The Simplest Way to Get Access to Vars in a Particular Scope

  1. Open Developer Tools > Resources (in Chrome)
  2. Open file with a function that has access to that scope (tip cmd/ctrl+p to find file)
  3. Set breakpoint inside that function and run your code
  4. When it stops at your breakpoint, you can access the scope var through console (or scope var window)

Note: You want to do this against un-minified js.

The Simplest Way to Show All Non-Private Vars

  1. Open Console (in Chrome)
  2. Type: this.window
  3. Hit Enter

Now you will see an object tree you can expand with all declared objects.

Timothy Perez
  • 20,154
  • 8
  • 51
  • 39
7

As everyone noticed: you can't. But you can create a obj and assign every var you declare to that obj. That way you can easily check out your vars:

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
rafaelcastrocouto
  • 11,781
  • 3
  • 38
  • 63
  • 1
    +1 thanks for cool example, only for completion the same can be seen also via `console.log(window);` http://jsfiddle.net/4x3Tx/ – Stano Aug 15 '12 at 11:11
  • 1
    Worth mentioning there is shorthand for this: `let a = v.a = [ 1, 2, 3 ];` – Gershom Maes Jul 20 '20 at 15:06
4

If you just want to inspect the variables manually to help debug, just fire up the debugger:

debugger;

Straight into the browser console.

David Meister
  • 3,941
  • 1
  • 26
  • 27
1

You can see scopes and their variables in [[Scopes]], even closure scopes using console.dir().

Example 1:

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

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

console.dir(counterWithClosure)

It shows you the variables in "[[Scopes]] > Closure", "[[Scopes]] > Global" and even "[[Scopes]] > Script" if the page have scripts reachable.

Image with output: console.dir output

Even with nested closures you can see the nested scopes.

Example 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) 

It shows all nested scopes in [[Scopes]] Image with output: console.dir with nested scopes

It works in Chromium-based browsers.

Johann Echavarria
  • 9,695
  • 4
  • 26
  • 32