javascript 在不使用此关键字的情况下在上下文中进行评估

javascript eval in context without using this keyword

我正在尝试在特定上下文中执行 eval。我发现答案 here 很有用。但是,我在 Chrome 版本 53.0.2785.143 m 中出现以下行为。其他浏览器没试过。我使用的代码如下:

function evalInContext(js, context) {
    return function() { return eval(js); }.call(context);
}


console.log(evalInContext('x==3', { x : 3})) // Throws
console.log(evalInContext('this.x==3', { x : 3})) // OK

但是我预计第一次调用 evalInContext 不会抛出。知道为什么会发生这种情况吗?

范围和上下文不一样

变量x的解析方式与上下文无关。它由范围规则解决:是否有任何闭包定义了该变量?如果没有,请查看全局对象。在任何时候,变量都不能通过查看上下文来解析。

你可以看看this article

该行为并非eval所特有,与其他函数相同:

"use strict";

var obj = { x : 3};
var y = 4;

function test() {
  console.log(this.x); // 3
  console.log(typeof x); // undefined
}

test.call(obj);

function test2() {
  console.log(this.y); // 4
  console.log(typeof y); // number
}

test2.call(window);

虽然我推荐@trincot 提供的答案,尤其是伟大的 link。我在这里发布我对我遇到的问题的解决方案

function evalInContext(scr, context)
{
    // execute script in private context
    return (new Function( "with(this) { return " + scr + "}")).call(context);
}

with(this)表达式允许context对象的成员变量出现在表达式scr.

的执行范围内

归功于 this answer 类似问题

这是另一种方法(用 ES6 编写):

 (new Function(...Object.keys(context), codeYouWantToExecute))(...Object.values(context));

感谢 Jonas Wilms 的回答。

这是我的解释,因为我最初并不了解它是如何工作的:

let example = new Function('a', 'b', 'return a+ b')

等同于创建函数赋值给example:

function(a,b) {
    return a + b
}

之后,您可以这样调用 exampleexample(2,6) 其中 returns 8.

但是假设您不想将 new Function 创建的函数分配给一个变量,而是想立即调用它。

那么你可以这样做:

new Function('a', 'b', 'return a+ b')(2,6)

此 post 顶部的代码片段本质上是做同样的事情。它使用扩展运算符传入 context 对象的键,如下所示: new Function(key1, key2, key3... lastkey, codeYouWantToExecute)。然后它调用新创建的函数并使用扩展运算符传递与每个键对应的值。

最后,这里是编译为 ES5 的答案:

(new (Function.bind.apply(Function, [void 0].concat(Object.keys(context), [codeYouWantToExecute])))()).apply(void 0, Object.values(context));

这会计算给定上下文中的表达式,而无需在 vars 前加上 'this'。

有警告但工作正常。

也可以在表达式中加入自定义函数。

只需调用一个 'evaluate' 函数,传入您的表达式、上下文和一个可选函数。

上下文必须是平面结构(无嵌套元素)并且函数必须在字符串表达式中引用为 'fn'。在这种情况下答案打印 11:

var expression = "fn((a+b)*c,2)";
var context = { a: 1, b: 2, c: 3 };
var func = function(x,y){return x+y;};

function evaluate(ex, ctx, fn) {
return eval("var "+JSON.stringify(ctx).replace(/["{}]/gi, "").replace(/:/gi, "=")+", fn="+fn.toString()+";"+ex);
}

var answer = evaluate(expression, context, func);
console.log(answer);

无法评论所以我 post,Joe 的回答很好,但如果字符串包含多个语句(如 x++; y++; x+y;),y++ 部分将永远不会被执行,因为它 returns 在第一个语句中。

通过此修改,将执行所有语句,最后一条语句将是 return 值。

function evalInScopeAndContext(scr, scopeAndContext)
{
    // execute script in private context
    return (new Function( "with(this) { return eval('" + scr + "'); }")).call(scopeAndContext);
}

我还更改了参数名称,因为该参数同时用作范围和上下文,它们是不同的东西。

警告 1:

使用此函数时,this.x 将等于 x 且无法更改,因为范围的 this 属性 如果存在,将被隐藏。

如果你执行evalInScopeAndContext('this', {a:1, b:2, this:3}) 答案将是 {a:1, b:2, this:3} 而不是 3.

要访问 属性 this,您必须改写 this.this,我认为它无法修复。

警告 2:

可以访问和修改外部作用域(从该函数的调用者到 window),除非被作用域对象隐藏。

因此,如果您打算执行类似 location = 'house'; 的操作但忘记在范围对象中插入 location 键,您将执行 window.location = 'house' 并可能造成损坏。

相反,this.location = 'house'; 总是安全的,因为 this 总是存在,并且只会将 location 键添加到提供的范围对象中,因此您可能希望始终使用 this. 作为前缀