calling eval() in particular context

前端 未结 14 1034
说谎
说谎 2020-11-27 17:05

I have following javaScript \"class\":

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


        
相关标签:
14条回答
  • So here we are in 2020 and I had this requirement and unfortunately most of the answers are simply not fit for the task.

    You can probably skip this part but to explain the situation... here's a more complete answer.

    Calling eval is bad... And here I am trying to do exactly that.. Not because I want but because someone forced me to do that... my other option would be to compile extract and AST and eventually evaluate the context of a value... But eval is exactly the tool for the task besides its own evil nature...

    So here we go, the mdn documentation:

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

    In short, eval used to work and now it's made more difficult to use as the context of evaluation will become the global context so in some case it's impossible to get this. And for some reasons, it didn't look like it could get the this as evaluation context using the answers from this question. So reading further we reach the second part of the article..

    Second part of the articles starts with "Never use eval()!", message received! Reading further we come to this:

    Fortunately, there's a very good alternative to eval(): simply using window.Function().

    Ok good!

    So looking at the code that seamed very good... Most of the example simply create a function call it and throw it away. This is pretty much how eval works so you could probably do that too...

    But for me, the eval context can be reused and potentially quite often so I came up with this.

    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};
      }                                                                                                                   
      `
    }
    

    This compiles a function that receives a context to be evaluated given a specific context. It's useful for cases where you know that the evaluation context will always contains a certain set of keys...

    The first line of the function generate a list of declared variables on the local scope by taking values from the context passed as a parameter.

    This will render JS code that look like this given a context: {a: 1, b: 2}

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

    The second line is the context you want to evaluate so for something like this 'a + b'

    It would render the following code:

    return a + b
    

    All in all, there is this utility method:

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

    That simply wrap it up and return the functor we need...

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

    The nice thing about it is that if you want to keep using the evaluator as you know you won't be changing the evaluation context much... Then you can save the evaluator and reuse it with different context..

    In my case it's to evaluate a context based on some records so the field set is the same but the values are different... So the method can be reused without having to compile more than one method... On the other plus side, it's supposedly faster than using eval and we're not evaling anything. And if the value passed to the Function would try to used thing outside of its scope... it would be less harmful than eval... For example, it has access to the global scope but shouldn't have access to the lexical scope. In other words...You're limited to the global scope, and the this passed to the call argument.

    And then if you really wanted to use it as an eval like function you could do this:

    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)
    }
    
    0 讨论(0)
  • 2020-11-27 17:46

    Another Bam!

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

    This example will keep yours context and send some data. In my case, is a some DOM selector

    0 讨论(0)
  • 2020-11-27 17:47

    I was struggling with this for a while in Angular, and found this answer the most useful. I was trying to implement some existing code which use 'with', not allowed by strict. My 'knife and fork' solution to not having to use 'this.' inside the expression I wanted to evaluate, and avoiding 'with' and 'eval' was:

    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;
    }
    

    This is working for expressions like (watched === 'hello') where 'watched' is a member of context.

    0 讨论(0)
  • 2020-11-27 17:49

    How to call eval in a given context? 3 words. Use a closure.

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

    Bam.

    0 讨论(0)
  • 2020-11-27 17:51

    Includes context plus a function plus an expression. The context must be a flat structure (no nested elements) and the function must be referred to as 'fn' inside the string expression. Answer prints 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);

    0 讨论(0)
  • 2020-11-27 17:53

    Here is an article which discussing running eval() in different contexts:

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

    Usually you do it with eval.call() or eval.apply().

    Here is also information regarding eval() and its use cases:

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

    0 讨论(0)
提交回复
热议问题