标题可能有些让人困惑, 在进入正文之前先举一个简单的例子:
function __eval(expr) {
return eval(expr);
}
__eval('var foo = 123;');
__eval('foo;'); // ReferenceError.
前一段时间做了一个远程控制台, 是公司 JavaScript 智能硬件平台 Ruff 某个项目的一个功能. 这个控制台在执行输入的表达式时有以下要求:
我编写了一个简单地 Babel 插件, 访问 (visit) VariableDeclaration
和 FunctionDeclaration
两种节点并将其转换为对 global 对象属性的赋值语句. 比如 var abc = 123;
会被转换为 global.abc = 123; undefined;
. 当然, 具体的转换会有一些细节处理, 使行为尽可能与预期一致.
不过最近几天, 一个同事突然对这个东西好奇起来并且觉得可能可以在不进行表达式转换的情况下实现对上下文的保留. 我一开始对这个想法的态度是: 妈蛋我对 JS 多了解? 说不行就不行. 不过在出门吃午饭的路上我意识到自己可能错了, 交换想法后我们各自拿出了一套实现.
以下是我的版本 (精简后):
var __eval = function () {
__eval = eval(`(${arguments.callee.toString()})`);
return eval(arguments[0]);
};
背后的点在于执行一个函数, 这个函数含有会 "递归" 执行它本身对应的表达式的 eval
. 可能这样有点难以理解, 接下来我们尝试展开对于函数的 eval
:
var __eval = function () {
__eval = function () {
__eval = eval(`(${arguments.callee.toString()})`);
return eval(arguments[0]);
};
return eval(arguments[0]);
};
再一层:
var __eval = function () {
__eval = function () {
__eval = function () {
__eval = eval(`(${arguments.callee.toString()})`);
return eval(arguments[0]);
};
return eval(arguments[0]);
};
return eval(arguments[0]);
};
所以每当 __eval
被调用时, 它会在上一次 __eval
创建的函数中再次创建新的 __eval
函数, 如此上下文就得到了保留!
然而这样做还是有其局限性. 一方面这使得整个链无法被释放, 另一方面新表达式中的声明会 "屏蔽" 掉之前执行的表达式中的声明. 然而递归 eval
的想法本身还是很有意思, 吧?
GitHub https://github.com/vilic
知乎 https://www.zhihu.com/people/vilicvane
扫码关注w3ctech微信公众号
共收到0条回复