我正试图在Javascript中破解Read-Eval-Print-Loop。 (这是基于网络的“自学Javascript”平台。)我有一些主要有用的东西,但是我遇到了一个关于闭包的奇怪错误。
这是循环核心的简化版本。我这样写是因为我想使用闭包和延续来维护eval
中创建的任何状态:
// listing 1
var repl = function(result) {
return {
result:
result,
then:
function (expression, continuation) {
return continuation(eval(expression));
},
};
}
它几乎可以工作。例如,它正确地评估了表达式var x = 1
,x++
,x
的序列:
// listing 2
repl(eval('var x = 1')).then('x++', repl)
.then('x', repl)
.result
// evaluates to 2
因此,表达式可以访问和修改先前声明的局部变量,而不会污染全局范围,这很好。 但变量声明(即var ...
)仅适用于链中的第一个表达式。例如,表达式序列var x = 1
,var y = 2
,{ {1}}抛出 y未定义错误:
y
我发现如果我用它的定义替换// listing 3
repl(eval('var x = 1')).then('var y = 2', repl)
.then('y', repl)
.result;
// throws "y is not defined"
的每个实例,我就可以避免这个错误,如下所示:
repl
这评估为2.所以我想我可以通过//listing 4
//
// same as listing 3, but repl is replaced with its
// definition from listing 1
function (result) {
return {
result:
result,
then:
function (expression, continuation) {
return continuation(eval(expression));
}
},
})(eval('var x = 1')).then(
'var y = 2',
function (result) {
return {
result:
result,
then:
function (expression, continuation) {
return continuation(eval(expression));
},
};
}
).then(
'y',
function (result) {
return {
result:
result,
then:
function (expression, continuation) {
return continuation(eval(expression));
},
};
}
).result
// evaluates to 2
每次迭代的eval
定义来解决我的问题。但肯定有更好的解决方案......不存在吗?
修改:我尝试用repl
替换repl
的每个实例,但它没有解决问题。我错过了什么?
答案 0 :(得分:2)
eval 创建的变量是在调用执行上下文中创建的。因此,它们仅可用于稍后在同一范围链上创建的其他执行上下文。
如果你这样做:
eval( 'var x = 3' );
作为全局代码,然后在计算表达式时,将全局上下文中的值为 3 的变量 x 创建为变量。如果从函数上下文中调用 eval ,那么任何变量声明都是该上下文的本地声明:
function doEval()
eval( 'var x = 3' );
}
当调用doEval()
时, x 将在函数的执行上下文中创建,并且只能在函数内部使用。
E.g:
function doEval() {
// Create local variable x
eval( 'var x = 3' );
// x is available on this function's scope chain
var foo = function(){alert(x)};
foo();
}
doEval() // shows 3
alert(x) // Refernce error, x is not defined
在您的代码中,每个函数调用都会创建一个新的执行上下文和变量环境,该环境无法访问前一个。您无法获取对本地上下文或变量对象的引用,因此您无法将其传递给其他函数。
要评估多个字符串作为代码,您可以使用对 eval 的顺序调用,如:
function evaluateExpressions() {
for (var i=0, iLen=arguments.length; i<iLen; i++) {
eval(arguments[i]);
}
}
并调用函数:
evaluateExpressions(expr1, expr2, expr3, ...)
然后你可能会这样做:
eval(expr1 + expr2 + expr3 + ...)
或
eval([expr1, expr1, expr3, ...].join(';'))
至少从函数内部调用它意味着变量不会意外地变为全局。
哦,差点忘了, eval是邪恶的。 ; - )
答案 1 :(得分:2)
由于RobG已经解释了问题的原因,我将这个答案限制在一个可能的解决方法。
如果您不介意污染全局范围(可能在这里甚至需要?),您可以使用indirect eval
call强制全局评估所有表达式:
var repl = function(result) {
return {
result:
result,
then:
function (expression, continuation) {
return continuation((1,eval)(expression));
},
};
}
您也可以拨打repl
而不是继续,因此您不需要每次都将其传递给then
:
var repl = function(result) {
return {
result:
result,
then:
function (expression) {
return repl((1,eval)(expression));
},
};
}
repl(eval('var x = 1')).then('var y = 2')
.then('y')
.result
Pitarou接受的解决方案隐藏在jsFiddle中,对此答案发表评论。作为参考,这里是“丑陋的黑客”解决方案的略微修改版本(它破坏了创建闭包的函数的源代码):
function make_repl() {
return {
result: undefined,
then: function (expression) {
return {
result: eval(expression),
then: eval('('+this.then.toString()+')'),
};
},
};
};
var repl = make_repl();
repl = repl.then('var x = 1').then('var y = 2').then('x + " " + y');
console.log(repl.result); // prints "1 2"
console.log(typeof(x), typeof(y));
// prints "undefined undefined", so we know the global scope was not touched