Is there any non-eval way to create a function with a runtime-determined name?
The Answer for ECMAScript 2015+ (aka "ES6"):
Yes. As of ES2015, the function created by an anonymous function expression assigned to an object property takes the name of that object property. This is implemented in all modern browsers, although Edge and Safari don't use the name in stack traces. We can use that in combination with another ES2015 feature (computed property names) to name a function without new Function
or eval
.
In ES2015 this creates a function named "foo###" where ### is 1-3 digits:
const dynamicName = "foo" + Math.floor(Math.random() * 1000);
const obj = {
[dynamicName]() {
throw new Error();
}
};
const f = obj[dynamicName];
// See its `name` property
console.log("Function's `name` property: " + f.name + " (see compatibility note)");
// We can see whether it has a name in stack traces via an exception
try {
f();
} catch (e) {
console.log(e.stack);
}
It would also work with [dynamicName]: function() { }
, method syntax isn't required, function syntax is fine. Which is handy if you want to create a constructor function this way:
const dynamicName = "Foo" + Math.floor(Math.random() * 1000);
const obj = {
[dynamicName]: function(throwError = false) {
if (throwError) {
throw new Error();
}
}
};
const F = obj[dynamicName];
// See its `name` property
console.log("Function's `name` property: " + F.name + " (see compatibility note)");
// We can see whether it has a name in stack traces via an exception
try {
new F(true);
} catch (e) {
console.log(e.stack);
}
// And we can see it works as a constructor:
const inst = new F();
console.log(inst instanceof F); // true
Of course, this is ES2015+, so you could also use class
to create a constructor, [dynamicName]: class { }
:
const dynamicName = "Foo" + Math.floor(Math.random() * 1000);
const obj = {
[dynamicName]: class {
constructor(throwError = false) {
if (throwError) {
throw new Error();
}
}
}
};
const F = obj[dynamicName];
// See its `name` property
console.log("Function's `name` property: " + F.name + " (see compatibility note)");
// We can see whether it has a name in stack traces via an exception
try {
new F(true);
} catch (e) {
console.log(e.stack);
}
// And we can see it works as a constructor:
const inst = new F();
console.log(inst instanceof F); // true
The Answer for ECMAScript 5 (from 2012):
No. You cannot do that without eval
or its cousin the Function
constructor. Your choices are:
Live with an anonymous function instead. Modern engines do things to help debugging with those.
Use
eval
.Use the
Function
constructor.
Details:
Live with an anonymous function instead. Many modern engines will show a useful name (e.g., in call stacks and such) if you have a nice, unambiguous
var name = function() { ... };
expression (showing the name of the variable), even though technically the function doesn't have a name. In ES6, functions created that way will actually have names if they can be inferred from the context. Either way, though, if you want a truly runtime-defined name (a name coming from a variable), you're pretty much stuck.Use
eval
.eval
is evil when you can avoid it, but with strings you're in total control of, in a scope you control, with an understanding of the costs (you're firing up a JavaScript parser), to do something you cannot do otherwise (as in this case), it's fine provided you really need to do that thing. But if you're not in control of the string or scope, or you don't want the cost, you'll have to live with an anonymous function.Here's how the
eval
option looks:var name = /* ...come up with the name... */; var f = eval( "(function() {\n" + " function " + name + "() {\n" + " console.log('Hi');\n" + " }\n" + " return " + name + ";\n" + "})();" );
Live example | Live source
That creates a function with the name we come up with at runtime without leaking the name into the containing scope (and without triggering the flawed handling of named function expressions in IE8 and earlier), assigning a reference to that function to
f
. (And it formats the code nicely so single-stepping through it in a debugger is easy.)This didn't used to correctly assign the name (surprisingly) in older versions of Firefox. As of the current version of their JavaScript engine in Firefox 29, it does.
Because that uses
eval
, the function you create has access to the scope in which it was created, which is important if you're a tidy coder who avoids global symbols. So this works, for instance:(function() { function display(msg) { var p = document.createElement('p'); p.innerHTML = String(msg); document.body.appendChild(p); } var name = /* ...come up with the name... */; var f = eval( "(function() {\n" + " function " + name + "() {\n" + " display('Hi');\n" + // <=== Change here to use the " }\n" + // function above " return " + name + ";\n" + "})();" ); })();
Use the
Function
constructor, as demonstrated in this article by Marcos Cáceres:var f = new Function( "return function " + name + "() {\n" + " display('Hi!');\n" + " debugger;\n" + "};" )();
Live example | Live source
There we create a temporary anonymous function (the one created via the
Function
constructor) and call it; that temporary anonymous function creates a named function using a named function expression. That will trigger the flawed handle of named function expressions in IE8 and earlier, but it doesn't matter, because the side-effects of that are limited to the temporary function.This is shorter than the
eval
version, but has an issue: Functions created via theFunction
constructor do not have access to the scope in which they were created. So the example above usingdisplay
would fail, becausedisplay
wouldn't be in-scope for the created function. (Here's an example of it failing. Source). So not an option for tidy coders avoiding global symbols, but useful for those times when you want to disassociate the generated function from the scope in which you're generating it.
Here's a utility function I came up with some time ago. It uses the Function
constructor technique as outlined in @T.J.Crowder's great answer, but improves on its disadvantages and allows fine-grained control over the scope of the new function.
function NamedFunction(name, args, body, scope, values) {
if (typeof args == "string")
values = scope, scope = body, body = args, args = [];
if (!Array.isArray(scope) || !Array.isArray(values)) {
if (typeof scope == "object") {
var keys = Object.keys(scope);
values = keys.map(function(p) { return scope[p]; });
scope = keys;
} else {
values = [];
scope = [];
}
}
return Function(scope, "function "+name+"("+args.join(", ")+") {\n"+body+"\n}\nreturn "+name+";").apply(null, values);
};
It allows you being tidy and avoiding complete access to your scope via eval
, e.g. in the above scenario:
var f = NamedFunction("fancyname", ["hi"], "display(hi);", {display:display});
f.toString(); // "function fancyname(hi) {
// display(hi);
// }"
f("Hi");