Search code examples
javascriptecmascript-6with-statementstrict-mode

How to replace `with` statement in strict mode


This code works optimally and is easily comprehensible:

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);
}
evalInScope("a + b", {a: 1, b: 2}); // 3 obviously, but fails in strict mode!

However, the "smart" brains decided to remove the with statement without a proper replacement.

Question: how to make it work again in ES6, which is automatically in strict mode?


Solution

  • Don't use eval, create a new Function instead. It won't inherit lexical strict mode - and even better, it won't inherit all your function-scoped and module-scoped variables:

    "use strict";
    
    function evalInScope(js, contextAsScope) {
      return new Function(`with (this) { return (${js}); }`).call(contextAsScope);
    }
    
    console.log(evalInScope("a + b", { a: 1, b: 2 })); // 3

    Also you don't get the weird "(last) statement result" return value that eval uses, but can either confine the js code to be an expression or include a return statement in the code itself.


    Alternatively, if you don't actually need to use a with statement with all its intricacies but just want to make a dynamic set of constant variables available to the evaled code, just generate the code for those constants dynamically. This allows to eval the code in strict mode even:

    "use strict";
    
    function evalInScope(js, contextAsScope) {
      return new Function(
        `"use strict";
        const {${Object.keys(contextAsScope).join(', ')}} = this;
        return (${js});`
      ).call(contextAsScope);
    }
    
    console.log(evalInScope("a + b", { a: 1, b: 2 })); // 3

    Or if the code doesn't use the this keyword itself, maybe also

    "use strict";
    
    function evalInScope(js, contextAsScope) {
      return new Function(
        '{' + Object.keys(contextAsScope).join(', ') + '}',
        `return (${js});`
      )(contextAsScope);
    }
    
    console.log(evalInScope("a + b", { a: 1, b: 2 })); // 3