Is there any non-invariant way to create a function with a run-time definition?

Is there a way to create a function with a real name defined at runtime without using eval and using only pure JavaScript? (Thus, there are no script elements generated because they are browser-specific [and would in many ways be masked eval anyway], without using non-standard functions from one particular JavaScript engine, etc.)

Note that I am not specifically asking about anonymous functions referenced by variables or properties with names, for example:

 // NOT this var name = /* ...come up with the name... */; var obj = {}; obj[name] = function() { /* ... */ }; 

There, when an object property has a name, the function does not work. Anonymous functions are good for many things, but not what I'm looking for here. I want the function to have a name (for example, displayed in call tables in debuggers, etc.).

+33
javascript
Feb 28 2018-12-12T00:
source share
2 answers

The answer for ECMAScript 2015+ (aka "ES6") :

Yes Starting with ES2015, a function created by an anonymous function expression assigned to an object property gets the name of this object property. This is implemented in all modern browsers, although Edge and Safari do not use the name in the stack trace. We can use this in conjunction with another ES2015 function (computed property names) to call functions without new Function or eval .

In ES2015, this creates a function called "foo ###", where ### has 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 '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); } 

(This will also work with [dynamicName]: function() { } , the method syntax is not required, the function syntax is fine.)




Answer for ECMAScript 5 (since 2012):

No. You cannot do this without eval or its cousin Function constructor. Your choice:

  1. Instead, live with an anonymous function. Modern engines do something to help debug them.

  2. Use eval .

  3. Use the Function constructor.

Details:

  1. Instead, live with an anonymous function. Many modern engines will show a useful name (for example, in call stacks, etc.), if you have a good, unique expression var name = function() { ... }; (showing the variable name), even if technically the function does not have a name. In ES6, functions created in this way will actually have names if they can be deduced from the context. In any case, if you need a name that is really specific at runtime (the name obtained from the variable), you are pretty much stuck.

  2. Use eval . eval is evil when you can avoid this, but with the help of lines that you have full control in the area you control, with an understanding of the costs (you run the JavaScript parser), do what you cannot do otherwise (as in this case ), this is good if you really need to do this. But if you do not manage the row or scope or do not want to pay for it, you will have to live with an anonymous function.

    This is what the eval option looks like:

     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 | Living source

    This creates a function with the name that we came up with at run time, without leaking the name to the containing area (and without starting incorrect processing of expressions of named functions in IE8 and earlier versions), assigning a link to this function f . (And it formats the code perfectly, so step-by-step execution in the debugger is easy.)

    This was not used for the correct name assignment (which is surprising) in older versions of Firefox. Starting with the current version of their JavaScript engine in Firefox 29, this is so.

    Since eval used in this case, the function you created has access to the area in which it was created, which is important if you are a neat encoder that avoids global characters. So this works, for example:

     (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" + "})();" ); })(); 
  3. Use the Function constructor, as shown in this article by Marcos Cáceres :

     var f = new Function( "return function " + name + "() {\n" + " display('Hi!');\n" + " debugger;\n" + "};" )(); 

    Live example | Living source

    There we create a temporary anonymous function (created using the Function constructor) and call it; this temporary anonymous function creates a named function using the expression of the named function. This will cause an invalid expression descriptor for named functions in IE8 and earlier, but it doesn’t matter since the side effects of this are limited by the time function.

    This is shorter than the eval version, but has a problem: functions created using the Function constructor do not have access to the area in which they were created. Thus, the above example using display will fail because display will not be in the scope of the created function. ( Here is an example of this failed. Source ). So it’s not an option for neat coders avoiding global characters, but is useful for cases where you want to separate the generated function from the area in which you generate it.

+42
Feb 28 2018-12-12T00:
source share

Here is the utility function with which I came some time ago. It uses the Function constructor, as pointed out by @TJCrowder, but improves on its shortcomings 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); }; 

This allows you to be neat and avoid full access to your area with eval , for example. in the above scenario:

 var f = NamedFunction("fancyname", ["hi"], "display(hi);", {display:display}); f.toString(); // "function fancyname(hi) { // display(hi); // }" f("Hi"); 
+7
Jun 04 '14 at 8:10
source share



All Articles