Obtention de toutes les variables dans la portée


194

Existe-t-il un moyen d'obtenir toutes les variables qui sont actuellement à portée en javascript?


Re votre réponse à Camsoft: C'est une question totalement différente; J'ai mis à jour ma réponse pour y répondre.
TJ Crowder du

3
C'est juste une question générale, être plus spécifique n'aidera pas beaucoup car je travaille avec une API obscure avec une mauvaise documentation.
Ian

Vous voulez dire des variables globales! Vous pouvez afficher les variables globales énumérables à l'aide de for (v in this) alert(v);. Cependant, tous les globaux ne sont pas énumérables, et je ne connais aucun moyen standard d'obtenir une liste des non énumérables.
Jason Orendorff

2
@Jason - Non, la question est claire. A l' intérieur d' une fonction des variables de périmètre comprennent des variables globales, this, arguments, les paramètres et toutes les variables définies dans enfermant les étendues.
Tim Down du

2
C'est pourquoi je manque les tables de symboles de Perl. Avez-vous l'intention de l'ajouter à une future version de Javascript?
Dexygen

Réponses:


84

Non. Les variables "dans la portée" sont déterminées par la "chaîne de portée", qui n'est pas accessible par programme.

Pour plus de détails (en grande partie), consultez la spécification ECMAScript (JavaScript). Voici un lien vers la page officielle où vous pouvez télécharger la spécification canonique (un PDF), et en voici un vers la version HTML officielle et linkable.

Mise à jour basée sur votre commentaire à Camsoft

Les variables dans la portée de votre fonction d'événement sont déterminées par l'endroit où vous définissez votre fonction d'événement, et non par la façon dont elles l'appellent. Mais , vous pouvez trouver des informations utiles sur ce qui est disponible pour votre fonction via thiset des arguments en faisant quelque chose dans le sens de ce que KennyTM a souligné ( for (var propName in ____)) car cela vous dira ce qui est disponible sur divers objets qui vous sont fournis ( thiset des arguments; si vous êtes vous ne savez pas quels arguments ils vous donnent, vous pouvez le découvrir via la argumentsvariable implicitement définie pour chaque fonction).

Ainsi, en plus de ce qui est dans la portée en raison de l'endroit où vous définissez votre fonction, vous pouvez découvrir ce qui est disponible par d'autres moyens en faisant:

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

(Vous pouvez développer cela pour obtenir des informations plus utiles.)

Au lieu de cela, cependant, j'utiliserais probablement un débogueur comme les outils de développement de Chrome (même si vous n'utilisez pas normalement Chrome pour le développement) ou Firebug (même si vous n'utilisez pas normalement Firefox pour le développement), ou Dragonfly sur Opera ou "Outils de développement F12" sur IE. Et lisez les fichiers JavaScript qu'ils vous fournissent. Et battez-les au-dessus de la tête pour obtenir des documents appropriés. :-)


En passant, Google Chrome a un excellent débogueur comparable à Firebug (avec un peu plus de glaçage également).
Pivotant

4
@Swivelgames: Oh, je préfère de loin les outils de développement de Chrome à Firefox + Firebug. Ils n'étaient tout simplement pas à la hauteur en 2010. :-)
TJ Crowder

1
@tjcrowder Effectivement! J'ai remarqué que l'horodatage de la réponse était il y a un certain temps :)
Swivel

98

Bien que tout le monde réponde " Non " et je sais que "Non" est la bonne réponse, mais si vous avez vraiment besoin d'obtenir des variables locales d'une fonction, il existe un moyen restreint.

Considérez cette fonction:

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

Vous pouvez convertir votre fonction en chaîne:

var s = f + '';

Vous obtiendrez la source de la fonction sous forme de chaîne

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

Vous pouvez maintenant utiliser un analyseur comme esprima pour analyser le code de fonction et trouver des déclarations de variables locales.

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

et trouver des objets avec:

obj.type == "VariableDeclaration"

dans le résultat (j'ai supprimé console.log(x)ci-dessous):

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

J'ai testé cela dans Chrome, Firefox et Node.

Mais le problème avec cette méthode est que vous n'avez que les variables définies dans la fonction elle-même. Par exemple pour celui-ci:

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

vous avez juste accès au x et non au y . Mais vous pouvez toujours utiliser des chaînes d' appel (arguments.callee.caller.caller.caller) dans une boucle pour trouver des variables locales des fonctions d'appel. Si vous avez tous les noms de variables locales, vous avez donc des variables d'étendue . Avec les noms de variables, vous avez accès aux valeurs avec un simple eval.


7
Excellente technique ici pour s'attaquer au problème d'origine. Mérite un vote positif.
deepelement

4
Les variables d'un appelant ne sont pas nécessairement des variables de portée. De plus, les variables d'étendue ne sont pas nécessairement dans la chaîne d'appel. Les variables qui ont été fermées peuvent être référencées par une fonction de fermeture lors de l'appel depuis un appelant qui est en dehors de la portée de la définition / fermeture (et dont les variables ne sont pas du tout accessibles à partir du corps de la fermeture).
DDS

Pourriez-vous expliquer clairement "une simple évaluation"? J'ai essayé avec le code ci-dessous, mais je n'ai pas pu obtenir de variable piégée dans la portée. function getCounter(start){ return function(){ start ++; return start; } } var counter = getCounter(1); var startInClosure = eval.call(counter, 'this.start;'); console.log(startInClosure);Il imprime undefinedalors que je m'attends à ce qu'il devrait être 2.
Pylipala

@Pylipala La question ici concerne les variables dans la portée qui n'obtiennent pas la valeur d'une variable locale de l'extérieur de la portée. Ce n'est pas possible, car il s'agit de la définition d'une variable locale qui ne devrait pas être accessible de l'extérieur. En ce qui concerne votre code, vous voulez dire le contexte et non la portée, mais vous n'avez pas mis start dans le contexte (this), vous ne pouvez donc pas le lire avec this.start . Voir ici: paste.ubuntu.com/11560449
iman

@iman Merci pour votre réponse. Je suppose également que ce n'est pas possible du côté Javascript, donc je suppose que c'est possible du côté v8. J'ai trouvé le lien de question ci-dessous qui décrit mon problème avec précision. Lasse Reichstein dit non mais mako-taco dit oui. J'ai essayé du code C ++ comme lien mais je ne sais pas comment l'écrire.
Pylipala

32

Oui et non. "Non" dans presque toutes les situations. «Oui», mais uniquement de manière limitée, si vous souhaitez vérifier la portée globale. Prenons l'exemple suivant:

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

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

Qui génère, parmi plus de 150 autres choses , les éléments suivants:

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

Il est donc possible de répertorier certaines variables dans la portée actuelle, mais ce n'est pas fiable, succinct, efficace ou facilement accessible.

Une meilleure question est de savoir pourquoi voulez-vous savoir quelles variables sont dans la portée?


2
Je pense que la question était plus générale et ne se limitait pas au javascript dans le contexte d'un navigateur Web.
Radu Simionescu

Changez simplement le mot "fenêtre" pour "global" si vous utilisez un nœud ... ou vous pouvez utiliser le mot "ceci" mais c'est interdit sous "utiliser strict"
Ivan Castellanos

J'ai ajouté une ligne vérifiant hasOwnProperty. Par exemple: for ( var i in window ) { if (window.hasOwnProperty(i)) { console.log(i, window[i]); }}. Cela réduira au moins les propriétés héritées et vos variables ne figureront que parmi environ 50 autres propriétés.
timctran

8
Pourquoi quelqu'un ne devrait-il pas au moins vouloir vérifier quelles variables sont dans la portée, lors du débogage et / ou du développement.
Dexygen

Une autre raison de vouloir savoir quelles variables sont de portée locale est de sérialiser une fermeture.
Michael

29

Dans ECMAScript 6, c'est plus ou moins possible en enveloppant le code dans une withinstruction avec un objet proxy. Notez qu'il nécessite un mode non strict et que c'est une mauvaise pratique.

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

Le proxy prétend être propriétaire de tous les identifiants référencés à l'intérieur with, donc les affectations de variables sont stockées dans la cible. Pour les recherches, le proxy récupère la valeur de la cible proxy ou de l'objet global (pas la portée parent). letetconst variables ne sont pas incluses.

Inspiré par cette réponse de Bergi .


1
C'est étonnamment réussi pour une technique aussi simple.
Jonathan Eunice

WOW super power
pery mimon

16

Tu ne peux pas.

Les variables, identificateurs de déclarations de fonction et arguments pour le code de fonction, sont liés en tant que propriétés de l' objet variable , qui n'est pas accessible.

Voir également:


1
Vrai, mais les variables de portée vont au-delà de l'objet variable actuel. Les variables dans la portée sont déterminées par la chaîne de portée , qui est une chaîne constituée (dans le cas normal) d'une série d'objets variables et se terminant par l'objet global (bien que l' withinstruction puisse être utilisée pour insérer d'autres objets dans la chaîne).
TJ Crowder du

+1 pour les liens vers bclary.com. Une version HTML de la spécification à jour apparaîtra dans les prochains mois sur le site Web de l'ECMA, je suis heureux de le dire.
TJ Crowder du

3
Juste une note, les deux liens sont rompus.
0xc0de

vous POUVEZ - avec analyse statique jsfiddle.net/mathheadinclouds/bvx1hpfn/11
mathheadinclouds

8

Le moyen le plus simple d'accéder à Vars dans une portée particulière

  1. Ouvrir les outils de développement> Ressources (dans Chrome)
  2. Ouvrir un fichier avec une fonction qui a accès à cette portée (astuce cmd / ctrl + p pour trouver le fichier)
  3. Définissez un point d'arrêt à l'intérieur de cette fonction et exécutez votre code
  4. Lorsqu'il s'arrête à votre point d'arrêt, vous pouvez accéder à la portée var via la console (ou la fenêtre var portée)

Remarque: Vous voulez faire cela contre des js non minifiés.

Le moyen le plus simple de montrer tous les Vars non privés

  1. Ouvrir la console (dans Chrome)
  2. Type: this.window
  3. Appuyez sur Entrée

Vous verrez maintenant une arborescence d'objets que vous pouvez développer avec tous les objets déclarés.


7

Comme tout le monde l'a remarqué: vous ne pouvez pas. Mais vous pouvez créer un obj et affecter chaque var que vous déclarez à cet obj. De cette façon, vous pouvez facilement vérifier vos 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

1
+1 merci pour l'exemple cool, seulement pour l'achèvement le même peut être vu aussi via console.log(window); jsfiddle.net/4x3Tx
Stano

5

J'ai fait un violon mettant en œuvre (essentiellement) les idées ci-dessus décrites par iman. Voici à quoi cela ressemble lorsque vous passez la souris sur le deuxième ipsum dansreturn ipsum*ipsum - ...

entrez la description de l'image ici

Les variables qui sont dans la portée sont mises en évidence là où elles sont déclarées (avec des couleurs différentes pour différentes portées). lelorem bordure rouge est une variable ombrée (pas dans la portée, mais dans la portée si l'autre lorem plus bas dans l'arbre ne serait pas là.)

J'utilise la bibliothèque esprima pour analyser le JavaScript, et estraverse, escodegen, escope (bibliothèques utilitaires au-dessus d'esprima.) Le `` gros travail '' est fait par toutes ces bibliothèques (la plus complexe étant esprima elle-même, bien sûr.)

Comment ça fonctionne

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

crée l'arbre de syntaxe abstrait. Ensuite,

analysis = escope.analyze(ast);

génère une structure de données complexe encapsulant des informations sur toutes les étendues du programme. Le reste rassemble les informations encodées dans cet objet d'analyse (et l'arbre de syntaxe abstrait lui-même), et en fait un schéma de coloration interactif.

La bonne réponse n'est donc pas «non», mais «oui, mais». Le "mais" étant un gros problème: vous devez essentiellement réécrire des parties importantes du navigateur Chrome (et c'est devtools) en JavaScript. JavaScript est un langage complet de Turing, ce qui est bien sûr possible en principe. Ce qui est impossible, c'est de faire le tout sans utiliser l'intégralité de votre code source (sous forme de chaîne), puis de faire des choses très complexes avec cela.


3

Si vous souhaitez simplement inspecter les variables manuellement pour faciliter le débogage, lancez simplement le débogueur:

debugger;

Directement dans la console du navigateur.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.