【问题标题】:calling eval() in particular context在特定上下文中调用 eval()
【发布时间】:2022-04-25 20:08:12
【问题描述】:

我有以下 javaScript“类”:

A = (function() {
   a = function() { eval(...) };
   A.prototype.b = function(arg1, arg2) { /* do something... */};
})();

现在让我们假设在 eval() 中我传递的字符串包含调用 b 并带有一些参数的表达式:

 b("foo", "bar")

但是我得到了 b 没有定义的错误。所以我的问题是:如何在 A 类的上下文中调用 eval?

【问题讨论】:

  • 你真的需要使用eval吗?这通常是错误代码的标志。
  • 是的,很遗憾我必须... 你可以相信我,我不想要,但我别无选择。所以请把这个问题当作有趣的案例来对待,而不是建议摆脱 eval。
  • 这个sn-p有很多错误。请发布功能代码。这段代码应该做什么?阅读这篇关于 JS 原型设计的文章:2ality.com/2011/11/javascript-classes.html
  • 您可以使用 Function.constructor(evalCode).call(thisContext, a,b, ...) 而不是 evalthis 和其他参数传递给您的评估代码。

标签: javascript


【解决方案1】:

实际上您可以通过函数通过抽象来完成此操作:

var context = { a: 1, b: 2, c: 3 };

function example() {
    console.log(this);
}

function evalInContext() {
    console.log(this);        //# .logs `{ a: 1, b: 2, c: 3 }`
    eval("example()");        //# .logs `{ a: 1, b: 2, c: 3 }` inside example()
}

evalInContext.call(context);

所以你 call 函数和你想要的 context 并在该函数内运行 eval

奇怪的是,这似乎对我在本地有效,但在 Plunkr!? 上无效?

对于一个简洁(并且可以说是多汁的;)版本,您可以将逐字复制到您的代码中,使用这个:

function evalInContext(js, context) {
    //# Return the results of the in-line anonymous function we .call with the passed context
    return function() { return eval(js); }.call(context);
}

编辑Don't confuse this and "scope".

//# Throws an error as `this` is missing
console.log(evalInContext('x==3', { x : 3}))

//# Works as `this` is prefixed
console.log(evalInContext('this.x==3', { x : 3})) 

而一个could do this

function evalInScope(js, contextAsScope) {
    //# Return the results of the in-line anonymous function we .call with the passed context
    return function() { with(this) { return eval(js); }; }.call(contextAsScope);
}

为了缩小差距,这不是 OP 的问题,它使用 withas MDN says

不建议使用 with 语句,因为它可能是来源 令人困惑的错误和兼容性问题。见“歧义反” 有关详细信息,请参见下面的“说明”部分。

但它确实有效,而且还不算太“糟糕”(不管那是什么意思),只要人们意识到这种调用可能会产生奇怪的情况。

【讨论】:

  • 您尝试过哪个浏览器(版本)?它不起作用,根据规范(以及在每个现代浏览器中),您在example 中获得this 值的undefined(或草率模式函数中的全局窗口对象)。
  • 我遇到了与此类似的问题,发现上述方法不起作用。我在这里发布了一个可能的解决方案:stackoverflow.com/a/40109254/1972493
  • 您确实应该更新您的答案以反映您在评论中提供的正确示例。
  • @est evalInScope("window", { window: 1 }) 返回1evalInContext 需要使用this.window(例如evalInContext("this.window", { window: 1 }))返回1
  • @Campbeln 解释了一切。谢谢!
【解决方案2】:

如何在给定的上下文中调用 eval? 3个字。使用闭包。

var result = function(str){
  return eval(str);
}.call(context,somestring);

砰。

【讨论】:

  • 多么优雅的解决方案
  • 这里somestring的目的是什么?
  • "somestring" 是您希望使用 eval 命令运行的代码。见developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 请注意,somestring 中的表达式必须使用 'this' 访问上下文属性,例如 this.property。否则你得到一个属性未定义的ReferenceError。 var context = {'live-usa': 'no', 'alcohol': 'never', 'height': '65', 'weight': '140' }; var 表达式 = "this.height == '' || this.weight == ''"; var result = function(str){ return eval(str); }.call(上下文,表达式); // 在这种情况下返回 false。
  • 如何在上下文中禁止访问全局变量?比如“窗口”“文档”
【解决方案3】:

编辑

尽管 eval.call 和 eval.apply 不会强制正确传递上下文,但您可以使用闭包来强制 eval 在@Campbeln 和@user3751385 的答案中提到的所需上下文中执行

我原来的答案

这是不可能的。 eval 仅在本地上下文(直接使用)或全局上下文(即使您使用 eval.call)中调用。

例如, a = {}; eval.call(a, "console.log(this);"); //prints out window, not a

欲了解更多信息,请查看这篇很棒的文章here

【讨论】:

  • 嗯...这并不完全正确。您可以使用特定的上下文执行eval,其中this === theContext 您传入(例如{ neek: true })。诀窍是你必须.call 另一个函数然后调用eval,你不能直接.call eval 并在你的例子中完成这个。在下面查看我的回答,了解如何完成此操作。
  • 不,不,...正如我在下面的回复中所述,这可以很容易地完成。
【解决方案4】:

肯定不是正确答案,请不要使用with 声明,除非您知道自己在做什么,但是对于好奇,您可以这样做

示例

    var a = {b: "foo"};
    with(a) {
        // prints "foo"
        console.log(eval("b"));  
        
        // however, "this.b" prints undefined
        console.log(eval("this.b"));
    
        // because "this" is still the window for eval
        // console.log(eval("this")); // prints window

// if you want to fix, you need to wrap with a function, as the main answer pointed out
        (function(){
	         console.log(eval("this.b")); // prints foo
        }).call(a);     
    }
    
    // so if you want to support both    
    with (a) {
    	(function (){
        console.log("--fix--");
      	console.log(eval("b")); // foo
        console.log(eval("this.b")); // foo
      }).call(a);
    }

with 是在函数中创建块作用域的失败尝试,就像 ES6's let 的设计目的一样。 (但不完全是,打开并阅读资源链接)

【讨论】:

  • 我认为这个答案绝对没问题。 with 语句是这个问题的完美解决方案。仅当您在任何地方或在大范围内使用“with”时,歧义才会成为问题。 :)
  • 这也是唯一允许您在不需要this 的情况下引用值的解决方案。
【解决方案5】:

这是一篇讨论在不同情况下运行eval() 的文章:

http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context

您通常使用eval.call()eval.apply() 来执行此操作。

这里还有关于eval() 及其用例的信息:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/eval

【讨论】:

  • 在“eval 很方便”处停止阅读第一篇文章,第二篇没有提到 eval 的任何用例,而是在常见新手错误的情况下如何替换 eval 的使用使用 eval。
  • eval.call 给了我一个错误:EvalError: The "this" object passed to eval must be the global object from which eval originated
  • @Rocket:是的,这种情况很少见,当然是在浏览器环境中。在 NodeJS 之类的东西中,它的模块系统通过将文件中的代码连接成一个开始和结束字符串,然后对其进行评估来工作。 jsFiddle 可能是另一个例子,但我假设。是的,我假设new Function 使用eval,或者至少与eval 共享代码。虽然有区别。
  • @Rocket:不确定这是否是浏览器环境,但可能是一个安全的选择。顺便说一句,您的 jsFiddle 不起作用,因为您没有传递 window 对象。
  • @Rocket:我认为您对 jsFiddle 的看法是正确的。由于他们正在使用框架获得一个新窗口,因此他们只需将您的代码包装在包含在该框架内容中的<script> 中。是的,eval.call()thisArg 似乎应该是封闭环境的全局对象。
【解决方案6】:

所以我们在 2020 年,我有这个要求,不幸的是,大多数答案根本不适合这项任务。

您可能可以跳过这部分,但要解释一下情况......这里有一个更完整的答案。

调用 eval 很糟糕...而在这里我正试图这样做...不是因为我想要,而是因为有人强迫我这样做...我的另一个选择是编译 extract 和 AST 并最终评估值的上下文...但是 eval 除了其自身的邪恶性质之外,它正是该任务的工具...

所以我们开始了,mdn 文档:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

简而言之,eval 曾经可以工作,但现在它变得更加难以使用,因为评估的上下文将成为全局上下文,因此在某些情况下不可能获得 this。并且由于某些原因,使用此问题的答案似乎无法将 this 作为评估上下文。 所以进一步阅读,我们到达了文章的第二部分..

文章的第二部分以“永远不要使用eval()!”开头,消息已收到!进一步阅读,我们得出以下结论:

幸运的是,eval() 有一个很好的替代方法:只需使用 window.Function().

好的!

所以看看接缝非常好的代码......大多数示例只是创建一个函数调用它并将其丢弃。这就是 eval 的工作原理,所以你也可以这样做......

但对我来说,eval 上下文可以重复使用,而且可能经常被重复使用,所以我想出了这个。

function create_context_function_template(eval_string, context) {
  return `
  return function (context) {
    "use strict";
    ${Object.keys(context).length > 0
      ? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
      : ``
    }
    return ${eval_string};
  }                                                                                                                   
  `
}

这会编译一个函数,该函数接收要在给定特定上下文的情况下评估的上下文。这对于您知道评估上下文将始终包含一组特定键的情况很有用...

函数的第一行通过从作为参数传递的上下文中获取值来生成本地范围内声明变量的列表。

这将在给定上下文的情况下呈现如下所示的 JS 代码:{a: 1, b: 2}

let a = context['a'], b = context['b'];

第二行是你想要评估的上下文,所以对于像这样的'a + b'

它将呈现以下代码:

return a + b

总而言之,有这个实用方法:

function make_context_evaluator(eval_string, context) {
  let template = create_context_function_template(eval_string, context)
  let functor = Function(template)
  return functor()
}

这只是简单地把它包装起来并返回我们需要的函子......

let context = {b: (a, b) => console.log(a, b)}
let evaluator = make_context_evaluator("b('foo', 'bar')", context)
let result = evaluator(context)

这样做的好处是,如果您想继续使用评估器,因为您知道您不会过多地更改评估上下文...然后您可以保存评估器并在不同的上下文中重用它..

在我的情况下,它是根据一些记录来评估上下文,因此字段集相同但值不同...因此可以重用该方法而无需编译多个方法...另一方面另外,据说它比使用eval 更快,而且我们没有评估任何东西。如果传递给Function 的值会尝试使用其范围之外的东西......它的危害会比eval 小......例如,它可以访问全局范围但不应该访问到词法范围。换句话说...您仅限于全局范围,并且 this 传递给调用参数。

如果你真的想将它用作类似 eval 的函数,你可以这样做:

function create_context_function_template(eval_string, context) {
  return `
  return function (context) {
    "use strict";
    ${Object.keys(context).length > 0
      ? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
      : ``
    }
    return ${eval_string};
  }                                                                                                                   
  `
}

function make_context_evaluator(eval_string, context) {
  let template = create_context_function_template(eval_string, context)
  let functor = Function(template)
  return functor()
}

function eval_like(text, context={}) {
   let evaluator = make_context_evaluator(text, context)
   return evaluator(context)
}

这里是一个如何使用的例子:

< eval_like("fun + 2", {fun: 1})
> 3

或者你甚至可以像这样传递一个对象。

> this.fun = 1
> eval_like("fun + 2", this)
> 3

您可以像函数声明一样传递表达式,但是...

> eval_like("() => fun", {fun: 2})()

它会起作用,但重要的是需要评估的所有内容都在上下文中。如果上下文值不存在,如果我没记错的话,Javascript 会尝试从全局对象中获取它们。

因此,为了正常工作,您必须确保代码中没有变量取自本地范围。

【讨论】:

  • 我复制粘贴了三个函数的最后一块,传递了我的上下文,得到了TypeError: this is undefined
  • @Michael 这可能是您尝试评估的代码中的错误。我将添加一个示例
【解决方案7】:

这解决了我的问题。

 function evalInContext(js, context) {
    return function(str){
        return eval(str);
    }.call(context, ' with(this) { ' + js + ' } ');
}

用于类似于“Dom-if”的实现

<template if="{{varInContext == ''}}"> ... </template>

例子

var myCtx = {table: 'Product', alias: 'ProductView'};
evalInContext(' table == "" ', myCtx); //#false
evalInContext(' alias == "ProductView" ', myCtx); //#true

【讨论】:

    【解决方案8】:
    var evalWithinContext = function(context, code)
    {
        (function(code) { eval(code); }).apply(context, [code]);
    };
    evalWithinContext(anyContext, anyString);
    

    【讨论】:

      【解决方案9】:

      各位,我想我已经有了明确的答案。它适用于 JavaScript(浏览器)和 NodeJs。

      function evalInContext(Context,toEval){
        return eval(`(function Main(){${toEval}})`).call(Context);
      }
      
      var context = {a:42,b:82,c:103};
      var toEval = "return this";
      
      console.log(evalInContext(context,toEval));//{"a": 42, "b": 82, "c": 103}

      在 Node v12.16.1、Node v14.7.0、Firefox v79.0 和 Google Chrome v84.0.4147.105 上测试

      【讨论】:

        【解决方案10】:

        又一个 Bam!

        eval('(function (data) {'+code+'})').call(selector,some_data);
        

        此示例将保留您的上下文并发送一些数据。就我而言,是一些 DOM 选择器

        【讨论】:

        • 在这种情况下some_data 是做什么用的?
        【解决方案11】:

        我在 Angular 中为此苦苦挣扎了一段时间,发现这个答案最有用。我试图实现一些使用'with'的现有代码,严格不允许。 我不必使用“这个”的“刀叉”解决方案。在我想要评估的表达式中,避免使用 'with' 和 'eval' 是:

        let evalInContext = function (js, context) {
            let keys = Object.keys(context);
            //let code = 'function it(){';
            let code = '';
            for (let i = 0; i < keys.length; i++){
                code += 'let '+keys[i]+' = window._evalincontextobj.'+keys[i]+';\n';
            }
            code += 'return (';
            code += js;
            code += ')';//}\n return it();';
            window['_evalincontextobj'] = context;
            let res = Function(code)();
            console.log(js+' = '+res);
            delete window['_evalincontextobj'];
            return res;
        }
        

        这适用于 (watched === 'hello') 这样的表达式,其中 'watched' 是上下文的成员。

        【讨论】:

          【解决方案12】:

          你可以使用我的图书馆https://github.com/marverix/meval

          const meval = require('meval');
          
          console.log(
            meval('item.a + item.b * 5', { item: { a: 2, b: 3 } })
          );
          
          // outputs: 17
          

          【讨论】:

          • 它是为简单的单行代码创建的,而不是整个 JS 库。所以请不要对答案投反对票。
          【解决方案13】:

          对我有用的是使用 Function 构造函数并在指定的上下文中调用它:

          var o = {
            x: 10
          }
          
          (new Function(`console.log(this.x)`)).call(o);

          不知何故,这在浏览器的控制台中不起作用,但在其他地方起作用。

          【讨论】:

            【解决方案14】:

            包括上下文加上一个函数加上一个表达式。上下文必须是平面结构(无嵌套元素),并且函数必须在字符串表达式中称为“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);

            【讨论】:

              【解决方案15】:

              对于 node.js 环境,您可以使用 safe-eval 在上下文中进行评估,例如(取自 https://www.npmjs.com/package/safe-eval):

              // your own context API - access to Node's process object and a custom function
              var code = '{pid: process.pid, apple: a()}'
              var context = {
                process: process,
                a: function () { return 'APPLE' }
              }
              var evaluated = safeEval(code, context) // { pid: 16987, apple: 'APPLE' }
              

              【讨论】:

                【解决方案16】:

                这似乎与Specify scope for eval() in JavaScript? 部分重复 所以我想我会交叉发布我的答案,以防它对专门设置scopeeval 的人有帮助。

                我的Function 基于构造函数的解决方案版本带有现代语法糖,我认为这是一个很好的解决方案,可以避免包含评估文本的内部变量污染范围。 (通过牺牲this 上下文,即使在use strict; 代码中,属性也可以是deleted)

                (如果您想软阻止/从范围中删除某些全局对象/方法,您可以将它们添加到范围中 undefined 并且它们将被覆盖(切勿将其用于不受信任的代码,有总是绕开它))

                class ScopedEval {
                    /** @param {Record<string,unknown>} scope */
                    constructor(scope) {
                        this.scope = scope;
                    }
                    eval(__script) {
                        return new Function(...Object.keys(this.scope),`
                                return eval(
                                    '"use strict";delete this.__script;' 
                                    + this.__script
                                );
                            `.replace(/[\n\t]|  +/g,'')
                        ).bind({__script})(...Object.values(this.scope));
                    }
                }
                

                我个人更喜欢在添加或调整范围以及评估某些代码时能够分开,因此可以像下面这样使用。

                const context = {
                  hi: 12,
                  x: () => 'this is x',
                  get xGet() {
                    return 'this is the xGet getter'
                  }
                };
                
                const x = new ScopedEval(context)
                console.log(x.eval('"hi = " + hi'));
                console.log(x.eval(`
                let k = x();
                "x() returns " + k
                `));
                console.log(x.eval('xGet'));
                
                x.scope.someId = 42;
                console.log(x.eval('(() => someId)()'))

                【讨论】:

                  【解决方案17】:

                  要在特定上下文中调用“eval”,请使用“call”方法:

                  var myscript = 'console.dir(this)';
                  context1 = document.querySelector('body');
                  context2 = document.querySelector('div');
                  
                  // This execute myscript in context1=body
                  eval('(function(){'+ myscript +'})').call(context1);
                  
                  // This execute myscript in context2=div
                  eval('(function(){' + myscript + '})').call(context2);
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2019-03-15
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-02-15
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多