是否有任何非eval方法来创建具有运行时确定名称的函数?

时间:2012-02-28 09:04:44

标签: javascript

有没有办法创建一个具有真实姓名的函数,该函数在运行时确定而不使用eval,并且仅使用纯JavaScript? (因此,没有生成的script元素,因为它们特定于浏览器环境[并且在很多方面无论如何都是eval伪装];不使用某个特定JavaScript引擎的非标准功能等。)

请注意,我特别询问由具有名称的变量或属性引用的匿名函数,例如:

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

虽然object属性有名称,但函数却没有。匿名函数适用于很多东西,但不是我在这里寻找的东西。我希望函数有一个名称(例如,在调试器中显示调用堆栈等)。

3 个答案:

答案 0 :(得分:40)

ECMAScript 2015的答案(又名“ES6”)

即可。从ES2015开始,由分配给对象属性的匿名函数表达式创建的函数将获取该对象属性的名称。 当我在2015年5月11日写这篇文章时,除了微软的Windows 10预览版“Project Spartan”之外,没有任何JavaScript引擎支持这个(是的,你读得对,M $在Mozilla 或 Google),但它符合规范并且实施将迎头赶上。 更新:截至2016年10月,最近版本的Chrome实施{{ 1}}以及分配名称的各种规则; Firefox仍然没有,但他们会到达那里。

例如,在ES2015中,这将创建一个名为“foo ###”的函数,其中###是1-3位数:

Function#name

使用新的ES2015评估属性名称和新方法语法来创建函数并为其指定动态名称,然后在const dynamicName = "foo" + Math.floor(Math.random() * 1000); const obj = { [dynamicName]() { throw new Error(); } }; const f = obj[dynamicName]; // See its `name` property (Edge and Chrome for now, eventually Firefox will get it) console.log("Function's `name` property: " + f.name + " (see compatibility note)"); // We can see that it truly has a name (even in Firefox which doesn't have the `name` property yet) via an exception: try { f(); } catch (e) { console.log(e.stack); }中获取对它的引用。 (它也适用于f,方法语法不是必需的,函数语法很好。)


ECMAScript 5的答案 (自2012年起)

没有。如果没有[dynamicName]: function() { }或其表兄eval构造函数,则无法执行此操作。您的选择是:

  1. 改为使用匿名函数。现代引擎可以帮助调试这些引擎。

  2. 使用Function

  3. 使用eval构造函数。

  4. 详细说明:

    1. 改为使用匿名函数。如果你有一个漂亮的,明确的Function表达式(显示变量的名称),许多现代引擎将显示一个有用的名称(例如,在调用堆栈等),即使技术上功能没有名称。在ES6中,如果可以从上下文中推断出,那么以这种方式创建的函数实际上将具有名称。但是,无论哪种方式,如果你想要一个真正的运行时定义的名称(来自变量的名称),你就会陷入困境。

    2. 使用var name = function() { ... };eval是邪恶的,当你可以避免它,但是在你控制的范围内,你可以完全控制你对成本的理解(你正在开火JavaScript解析器),做不能做的事情(就像在这种情况下一样),如果你真的需要做那件事就没关系。但是,如果您无法控制字符串或范围,或者您不想要成本,则必须使用匿名函数。

      以下是eval选项的外观:

      eval

      Live example | Live source

      使用我们在运行时提出的名称创建一个函数,而不会将名称泄漏到包含范围中(并且不会触发IE8及更早版本中对命名函数表达式的错误处理),将该函数的引用分配给{{ 1}}。 (并且它很好地格式化代码,因此在调试器中单步执行它很容易。)

      在旧版本的Firefox中,这并没有用来正确地指定名称(令人惊讶)。截至Firefox 29中当前版本的JavaScript引擎,它确实如此。

      因为它使用var name = /* ...come up with the name... */; var f = eval( "(function() {\n" + " function " + name + "() {\n" + " console.log('Hi');\n" + " }\n" + " return " + name + ";\n" + "})();" ); ,所以您创建的函数可以访问创建它的范围,如果您是一个避免全局符号的整洁编码器,这很重要。所以这有用,例如:

      f
    3. 使用eval构造函数,如this article by Marcos Cáceres

      中所示
      (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" +
              "})();"
          );
      })();
      

      Live example | Live source

      我们创建了一个临时匿名函数(通过Function构造函数创建的函数)并调用它;临时匿名函数使用命名函数表达式创建命名函数。 在IE8及更早版本中触发命名函数表达式的有缺陷句柄,但这并不重要,因为它的副作用仅限于临时函数。

      这比var f = new Function( "return function " + name + "() {\n" + " display('Hi!');\n" + " debugger;\n" + "};" )(); 版本短,但有一个问题:通过Function构造函数创建的函数而不是可以访问创建它们的范围。因此,使用eval的上述示例将失败,因为Function不在创建函数的范围内。 (Here's an example失败了。Source)。所以不是整理编码器避免全局符号的选项,但对于希望将生成的函数与生成函数的范围解除关联时有用。

答案 1 :(得分:7)

这是我前段时间提出的实用功能。它使用@ T.J.Crowder的优秀答案中概述的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);
};

它允许您整洁,以避免通过eval完全访问您的范围,例如在上面的场景中:

var f = NamedFunction("fancyname", ["hi"], "display(hi);", {display:display});
f.toString(); // "function fancyname(hi) {
              // display(hi);
              // }"
f("Hi");

答案 2 :(得分:1)

还有

let f = function test(){};
Object.defineProperty(f, "name", { value: "New Name" });

将完成与 the accepted answer 相同的

console.log(f.name) // New Name

但是在打印函数时都不显示“新名称” console.log(f) // test(){}