【问题标题】:Specify scope for eval() in JavaScript?在 JavaScript 中指定 eval() 的范围?
【发布时间】:2012-04-04 14:16:01
【问题描述】:

有什么方法可以在特定范围(但不是全局)上执行 eval()?

例如,以下代码不起作用(a 在第二条语句中未定义),因为它们位于不同的范围内:

eval(var a = 1); 
eval(alert(a));

如果可能,我想动态创建一个范围。例如(语法肯定是错误的,但只是为了说明这个想法)

var scope1;
var scope2;
with scope1{
    eval(var a = 1); eval(alert(a));  // this will alert 1
}
with scope2{
    eval(var a = 1); eval(a++); eval(alert(a));  // this will alert 2
}
with scope1{
    eval(a += 2); eval(alert(a)); // this will alert 3 because a is already defined in scope1
}

知道如何实现这样的目标吗?谢谢!

【问题讨论】:

标签: javascript scope eval


【解决方案1】:

您可以使用 "use strict" 在 eval 本身中包含经过评估的代码。

其次,eval 的严格模式代码不会将新变量引入周围范围。在普通代码中 eval("var x;") 将变量 x 引入到周围函数或全局范围中。这意味着,一般来说,在包含对 eval 的调用的函数中,每个不引用参数或局部变量的名称必须在运行时映射到特定的定义(因为 eval 可能已经引入了一个新变量,它将隐藏外部变量)。 在严格模式下eval 仅为正在评估的代码创建变量,因此 eval 无法影响名称是指外部变量还是某个局部变量

var x = 17;                                       //a local variable
var evalX = eval("'use strict'; var x = 42; x");  //eval an x internally
assert(x === 17);                                 //x is still 17 here
assert(evalX === 42);                             //evalX takes 42 from eval'ed x

如果一个函数声明为“use strict”,那么其中的所有内容都将在严格模式下执行。以下将与上述相同:

function foo(){
    "use strict";

     var x = 17;
     var evalX = eval("var x = 42; x");
     assert(x === 17);
     assert(evalX === 42);
}

【讨论】:

  • 附带说明,使用 'use strict' with 将导致异常。 Quote: with(){} 语句在启用严格模式时失效 - 实际上它甚至显示为语法错误。来源:ejohn.org/blog/ecmascript-5-strict-mode-json-and-more
  • @Joseph 梦想家它对我不起作用。我用 Chrome 试了一下。哪个浏览器应该支持这个?
  • 这段代码似乎没有达到 OP 的要求。这似乎可以防止对eval 的调用更改它们被调用的范围的内容,但询问者希望允许eval 的调用者控制eval 在哪个范围内运行,但不阻止修改(他的示例它的工作原理包含这样的修改,很明显这是他想要的)
【解决方案2】:

将您希望存在于您的范围内的变量创建为函数中的局部变量。然后,从该函数返回一个本地定义的函数,该函数具有单个参数并在其上调用 evaleval 的该实例将使用其包含函数的范围,该范围嵌套在您的顶级函数的范围内。顶级函数的每次调用都会创建一个带有 eval 函数的新实例的新范围。为了让一切保持动态,您甚至可以在顶级函数中调用 eval 来声明该范围内的本地变量。

示例代码:

function makeEvalContext (declarations)
{
    eval(declarations);
    return function (str) { eval(str); }
}

eval1 = makeEvalContext ("var x;");
eval2 = makeEvalContext ("var x;");

eval1("x = 'first context';");
eval2("x = 'second context';");
eval1("window.alert(x);");
eval2("window.alert(x);");

https://jsfiddle.net/zgs73ret/

【讨论】:

  • 但这在“使用严格”模式下不起作用
【解决方案3】:

简单得像馅饼。

// Courtesy of Hypersoft-Systems: U.-S.-A.
function scopeEval(scope, script) {
  return Function('"use strict";return (' + script + ')').bind(scope)();
}

scopeEval(document, 'alert(this)');

【讨论】:

  • Function.bind(scope) 解决了我的问题。非常感谢!
  • 以下代码将不在scopeEval('Evaluated inside scope', "new Function('alert(this)')()")范围内
  • bind 方法绑定函数的接收者 (this),不是范围。这意味着您必须将所有变量称为this.varname
【解决方案4】:

这是一个 20 行左右的 JS 类,它在词法范围内使用 eval 实现可扩展上下文:

// Scope class
//   aScope.eval(str) -- eval a string within the scope
//   aScope.newNames(name...) - adds vars to the scope
function Scope() {
  "use strict";
  this.names = [];
  this.eval = function(s) {
    return eval(s);
  };
}

Scope.prototype.newNames = function() {
  "use strict";
  var names = [].slice.call(arguments);
  var newNames = names.filter((x)=> !this.names.includes(x));

  if (newNames.length) {
    var i, len;
    var totalNames = newNames.concat(this.names);
    var code = "(function() {\n";

    for (i = 0, len = newNames.length; i < len; i++) {
      code += 'var ' + newNames[i] + ' = null;\n';
    }
    code += 'return function(str) {return eval(str)};\n})()';
    this.eval = this.eval(code);
    this.names = totalNames;
  }
}


// LOGGING FOR EXAMPLE RUN
function log(s, eval, expr) {
	s = '<span class="remark">' + String(s);
  if (expr) {
    s += ':\n<b>' + expr + '</b>   -->   ';
  }
  s += '</span>';
  if (expr) {
    try {
      s += '<span class="result">' + JSON.stringify(eval(expr)) + '</span>';
    } catch (err) {
      s += '<span class="error">' + err.message + '</span>';
    }
  }
  document.body.innerHTML += s + '\n\n';
}
document.body.innerHTML = '';


// EXAMPLE RUN
var scope = new Scope();
log("Evaluating a var statement doesn't change the scope but newNames does (should return undefined)", scope.eval, 'var x = 4')
log("X in the scope object should raise 'x not defined' error", scope.eval, 'x');
log("X in the global scope should raise 'x not defined' error", eval, 'x');
log("Adding X and Y to the scope object");
scope.newNames('x', 'y');
log("Assigning x and y", scope.eval, 'x = 3; y = 4');
log("X in the global scope should still raise 'x not defined' error", eval, 'x');
log("X + Y in the scope object should be 7", scope.eval, 'x + y');
log("X + Y in the global scope should raise 'x not defined' error", eval, 'x + y');
.remark {
  font-style: italic;
}

.result, .error {
  font-weight: bold;
}

.error {
  color: red;
}
&lt;body style='white-space: pre'&gt;&lt;/body&gt;

【讨论】:

  • 很好的补充和很好的例子,谢谢!我想知道是否有办法将值“注入”到范围中。例如scope.define("x", 3)?
  • 要向范围“添加变量”,您可以在旧范围内使用新变量创建一个新范围。新作用域将继承旧作用域的变量。不过,这不会更改旧范围的变量集。您可以将旧范围的 eval 属性分配给新范围的 eval 值。
【解决方案5】:

这对我来说效果最好:

const scopedEval = (scope, script) => Function(`"use strict"; ${script}`).bind(scope)();

用法:

scopedEval({a:1,b:2},"return this.a+this.b")

【讨论】:

  • 太棒了!这个惊人的小sn-p代码是我发现执行注入ShadowRoot的脚本的唯一方法,该脚本可以在该范围内实际交互。
【解决方案6】:

您可以查看vm-browserify 项目,该项目将与browserify 结合使用。

它的工作原理是创建&lt;iframe&gt;s 和evaling &lt;iframe&gt; 中的代码。代码实际上非常简单,因此如果您不想使用库本身,可以根据自己的目的调整基本思想。

【讨论】:

    【解决方案7】:

    这里的方法是允许上下文对象参数化表达式的评估。

    首先使用Function() constructor 创建一个函数,该函数接受上下文的每个键以及要计算的表达式;主体返回评估的表达式。然后使用上下文的所有值和要计算的表达式调用该函数。

    function scopedEval(context, expr) {
        const evaluator = Function.apply(null, [...Object.keys(context), 'expr', "return eval('expr = undefined;' + expr)"]);
        return evaluator.apply(null, [...Object.values(context), expr]);
    }
    
    // Usage
    const context = {a: 1, b: 2, c: {d: 3}};
    scopedEval(context, "a+b+c.d");  // 6
    

    通过使用Function.prototype.apply,不需要事先知道参数的数量和名称。因为参数的作用域是 evaluator 函数,所以可以从表达式直接访问它们(而不需要 this.a)。

    【讨论】:

    • 不错的解决方案。然而,除了上下文之外,也可以获得表达式的值。我会确保取消设置:"return eval('expr = undefined;' + expr)"
    • @AngelPolitis 不错,已更新。
    • 在您的解决方案中,您不能在上下文中使用“expr”属性。我修复它。看我的回答
    【解决方案8】:

    穷人的方法:

    如果您的范围不是太动态,只需几个静态和只读声明,只需将其放在一个字符串中,然后将您想要执行的字符串与该字符串连接起来:

      const scopeAll = `
        const myFunc = (a, b) => a + b + s; 
      `
    
      const scope1  = `
        ${scopeAll}
        const s = 'c';
      `
      const scope2  = `
        ${scopeAll}
        const s = 'd';
      `
      
      const myStringToExecute = `
        myFunc('a', 'b')
      `
      
      
      console.log(eval(scope1 + myStringToExecute));
      console.log(eval(scope2 + myStringToExecute));

    【讨论】:

      【解决方案9】:

      这是我发现的最简单的方法,但它不使用 eval。

      function execInContext(code, context)
      {   
          return Function(...Object.keys(context), 'return '+ code (...Object.values(context));
      }
      

      这里我们创建了一个Function 对象。 Function 构造函数接受一个参数数组,最后一个是函数要执行的代码,其他的都是函数的参数名称。我们正在做的是创建一个函数,该函数的参数与context 对象中的字段名称相同,然后使用context 中的字段值调用此函数。所以如果你打电话

      execInContext('myVar', {myVar: 'hi!'});
      

      和这个一样

      ((myVar) => { return myVar; })('hi!');
      

      结果将是hi!

      【讨论】:

        【解决方案10】:

        简单

        const evaluate = (context, expr) =>
                Function(Object.keys(context).join(','), `return ${expr}`)
                (...Object.values(context));
        

        用法

        const result = evaluate({a: 1, b: 2, c: {d: 3}}, "a+b+c.d"); // 6
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-04-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-08-13
          • 2013-05-25
          相关资源
          最近更新 更多