prototype
prototype

Reputation: 7970

Specify object to use as global scope in new Function constructor?

Is there a way in Javascript to specify an object to use as the global scope when creating a function using new Function(...)?

For instance, consider a string

  var str = "return foo()";  // user supplied, generated by a trusted program

we can create a function with this string, passing in certain arguments

  var fn = new Function(str);
  var result = fn() 

This works if foo is defined in the global scope, either window in a browser or GLOBAL.global in Node.js.

But is there a way to give this newly created function an object, e.g. {foo: function() { return 42; }} and tell it to use that as the global scope?

The motivation here is the universe of possible functions that might be assumed to exist on the global scope is unknown. I'd like to define an object using ES6 Proxy that provides default implementations to unanticipated functions, and pass that object in to be used as the global scope within the function.

I know it's possible to explicitly define and pass in specific arguments, e.g.

  var fn = new Function("foo", str);
  var result = fn(function() { return "bar"; })

but that won't work as i'd like to handle methods we didn't anticipate.

It's also possible to pass in an object, e.g.

 var scope = { foo: function() { return 42; }
 var fn = new Function("scope", str);
 var result = fn(scope)

but that won't work as the string says "return foo()" not "return scope.foo()"

Define a scope for javascript Function constructor

EDIT

Answer suggested from comments by @ChiragRavindra and @RobG...

 var str = "return 'sand'+foo(); "  
 var o = {foo: function() { return "bar"}}
 var fn = new Function("obj", "with (obj) { " + str + "}")
 fn = fn.bind(o,o)
 fn(str) //==> "sandbar"

Upvotes: 4

Views: 1804

Answers (2)

br3nt
br3nt

Reputation: 9586

The use of with is deprecated, and is forbidden in strict mode (MDN docs).

Here is an alternative approach.

We can make all the properties of obj available to the user-defined fnBody using Object.keys() and Object.values().

function createFunction(obj, fnParams, fnBody) {
  const objParams = Object.keys(obj)
  const objArgs = Object.values(obj)
  const fn = new Function(...objParams, ...fnParams, fnBody)
  return (fnArgs = []) => fn(...objArgs, ...fnArgs)
}

const outer = "Hello from the outside!"

const scope = {
  get inner() {
    return "Hello from the inside!"
  }
}

createFunction(scope, [], `console.log('inner:', inner)`)()
createFunction(scope, [], `console.log('outer:', outer)`)()

It is important to note that the closure returned by createFunction() is decoupled from obj as objParams and objArgs are extracted from obj.

If obj is changed after the call to createFunction(), e.g. via delete obj.inner or obj.new_property = 17, or obj.inner = 'something else', fnBody will only have access to the values at the time createFunction() was called.

This is a good thing from a "pure function" perspective. It ensures the function has a well-defined unchanging API that fnBody can rely upon.

Upvotes: 1

Patrick Roberts
Patrick Roberts

Reputation: 51886

Yes, you can. Define a Proxy that resolves every property with a function. So, to implement your second example, we can do this:

function factory (string) {
  const callable = new Function(`with(this){${string}}`);

  return function fn (method) {
    return callable.call(new Proxy({}, {
      has () { return true; },
      get (target, key) { if (typeof key !== 'symbol') return method; }
    }));
  };
}

const fn = factory('return foo()');
const result = fn(() => 42);

console.log(result);

Upvotes: 5

Related Questions