Reputation: 7970
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
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
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