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

T.J*_*der 33 javascript

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

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

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

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

T.J*_*der 45

ECMAScript 2015的答案(又名"ES6"):

是的.从ES2015开始,由分配给对象属性的匿名函数表达式创建的函数将获取该对象属性的名称.正如我在2015年5月11日写的那样,除了微软的Windows 10预览版"Project Spartan"之外,没有任何JavaScript引擎支持这一点(是的,你读的是正确的,M $在Mozilla 谷歌之前到达),但它在规范和实现将迎头赶上. 更新:截至2016年10月,Chrome的最新版本实施new Function以及分配名称的各种规则; Firefox仍然没有,但他们会到达那里.

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

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);
}
Run Code Online (Sandbox Code Playgroud)

它使用新的ES2015评估属性名称和新方法语法来创建函数并为其提供动态名称,然后获取对它的引用eval.(它也可以使用[dynamicName]: function() { },方法语法不是必需的,函数语法很好.)


ECMAScript 5的答案 (自2012年起):

不,如果没有eval或者它的堂兄是Function构造函数,你就不能这样做.你的选择是:

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

  2. 使用eval.

  3. 使用Function构造函数.

细节:

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

  2. 使用eval.eval是邪恶的,当你能避免它,但字符串你在总量控制,在自己控制的范围内,与成本的理解(你射击了一个JavaScript解析器),做一些你无法以其他方式做(就像在这种情况下),如果你真的需要做那件事就没关系.但是,如果您无法控制字符串或范围,或者您不想要成本,则必须使用匿名函数.

    以下是该eval选项的外观:

    var name = /* ...come up with the name... */;
    var f = eval(
        "(function() {\n" +
        "   function " + name + "() {\n" +
        "       console.log('Hi');\n" +
        "   }\n" +
        "   return " + name + ";\n" +
        "})();"
    );
    
    Run Code Online (Sandbox Code Playgroud)

    实例 | 直播源

    这将创建我们在运行时拿出不漏名称为包含范围内(并且没有触发IE8有名函数表达式的缺陷处理和更早版本)名称的功能,赋予此函数的引用f.(并且它很好地格式化代码,因此在调试器中单步执行它很容易.)

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

    因为它使用eval,您创建的函数可以访问创建它的范围,如果您是一个避免全局符号的整洁编码器,这很重要.所以这有用,例如:

    (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" +
            "})();"
        );
    })();
    
    Run Code Online (Sandbox Code Playgroud)
  3. 使用Function构造函数,如MarcosCáceres在本文中所示:

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

    实例 | 直播源

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

    这比eval版本短,但有一个问题:通过Function构造函数创建的函数无权访问创建它们的作用域.因此上面使用的示例display将失败,因为它display不在创建函数的范围内.(这是一个失败的例子.来源).因此,不是整理编码器避免全局符号的选项,但是当您想要将生成的函数与生成函数的范围解除关联时,这些选项很有用.

  • 嗯,我看到你了.好吧,我会考虑避免使用匿名函数.谢谢! (2认同)

Ber*_*rgi 7

这是我前段时间提出的实用功能.它使用了Function@TJCrowder的优秀答案中概述的构造函数,但改进了它的缺点并允许对新函数的范围进行细粒度控制.

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);
};
Run Code Online (Sandbox Code Playgroud)

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

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