J'ai écrit (en JavaScript) une boucle interactive de lecture-évaluation-imputation qui est encapsulée dans le logiciel dans un objet. Cependant, j'ai récemment remarqué que les définitions de fonctions de haut niveau spécifiées à l'interpréteur ne semblent pas être "mémorisées" par l'interpréteur. Après quelques travaux de diagnostic, j'ai réduit le problème principal à ceci :
var evaler = {
eval: function (str)
{
return eval(str);
},
};
eval("function t1() { return 1; }"); // GOOD
evaler.eval("function t2() { return 2; }"); // FAIL
À ce stade, j'espère que les deux déclarations suivantes fonctionneront comme prévu :
print(t1()); // => Results in 1 (This works)
print(t2()); // => Results in 2 (this fails with an error that t2 is undefined.)
Ce que j'obtiens à la place, c'est la valeur attendue pour l'élément t1
et la ligne t2
échoue avec l'erreur suivante t2
n'est pas liée.
IOW : Après avoir exécuté ce script, j'ai une définition pour t1
et aucune définition pour t2
. L'acte d'appeler l'approbation de l'intérieur evaler
est suffisamment différent de l'appel au niveau supérieur pour que la définition globale ne soit pas enregistrée. Ce qui se passe, c'est que l'appel à evaler.eval
renvoie un objet fonction, je suppose donc que t2
est défini et stocké dans un autre ensemble de liens auxquels je n'ai pas accès. (Il n'est pas défini en tant que membre de evaler
.)
Existe-t-il une solution simple pour résoudre ce problème ? J'ai essayé toutes sortes de solutions, mais je n'en ai pas trouvé une qui fonctionne. (La plupart de mes tentatives ont consisté à placer l'appel à eval dans une fonction anonyme, et à modifier la façon dont elle est appelée, en changeant la fonction __parent__
etc.)
Des idées sur la manière de résoudre ce problème ?
Voici le résultat d'une enquête plus approfondie :
tl;dr : Rhino ajoute un scope intermédiaire à la chaîne de scope lors de l'appel d'une méthode sur une instance. t2
est en cours de définition dans cette portée intermédiaire, qui est immédiatement rejetée. @Matt : Votre approche "hacky" pourrait bien être la meilleure façon de résoudre ce problème.
Je travaille encore sur la cause première, mais grâce au temps passé avec jdb, j'ai maintenant une meilleure compréhension de ce qui se passe. Comme cela a été discuté, une déclaration de fonction comme function t1() { return 42; }
fait deux choses.
- Il crée une instance anonyme d'un objet fonction, comme vous l'obtiendriez avec l'expression
function() { return 42; }
- Il lie cette fonction anonyme à la portée supérieure actuelle avec le nom
t1
.
Ma question initiale est de savoir pourquoi je ne vois pas la seconde de ces choses se produire lorsque j'appelle eval
à partir d'une méthode d'un objet.
Le code qui effectue la liaison dans Rhino semble se trouver dans la fonction org.mozilla.javascript.ScriptRuntime.initFunction
.
if (type == FunctionNode.FUNCTION_STATEMENT) {
....
scope.put(name, scope, function);
Pour les t1
cas ci-dessus, scope
est ce que j'ai défini comme étant mon champ d'application de premier niveau. C'est là que je veux que mes fonctions de premier niveau soient définies, c'est donc un résultat attendu :
main[1] print function.getFunctionName()
function.getFunctionName() = "t1"
main[1] print scope
scope = "com.me.testprogram.Main@24148662"
Toutefois, dans la t2
cas, scope
est tout à fait différent :
main[1] print function.getFunctionName()
function.getFunctionName() = "t2"
main[1] print scope
scope = "org.mozilla.javascript.NativeCall@23abcc03"
Et c'est le champ d'application parent de ce NativeCall
c'est le champ d'application que j'attends au plus haut niveau :
main[1] print scope.getParentScope()
scope.getParentScope() = "com.me.testprogram.Main@24148662"
C'est plus ou moins ce que je craignais en écrivant ce qui précède : "Dans le cas de l'évaluation directe, t2 est lié à l'environnement global. Dans le cas de l'évaluateur, il est lié 'ailleurs'" Dans ce cas, 'ailleurs' s'avère être l'instance de NativeCall
... le t2
est créée, liée à une fonction t2
dans la variable NativeCall
et le NativeCall
disparaît lorsque l'appel à evaler.eval
les retours.
Et c'est là que les choses deviennent un peu floues... Je n'ai pas fait autant d'analyses que je le souhaiterais, mais ma théorie de travail actuelle est que le NativeCall
Le champ d'application est nécessaire pour garantir que this
pointe vers evaler
lors de l'exécution de l'appel à evaler.eval
. (Si l'on recule un peu dans le temps, la NativeCall
est ajouté à la chaîne de portée par Interpreter.initFrame
lorsque la fonction "a besoin d'être activée" et que son type de fonction n'est pas nul. Je suppose que ces choses ne sont vraies que pour les invocations de fonctions simples, mais je n'ai pas suffisamment remonté le fil du temps pour en être sûr. Peut-être demain).