是否有任何非评估方法可以创建具有运行时确定名称的函数?

Is there any non-eval way to create a function with a runtime-determined name?

提问人:T.J. Crowder 提问时间:2/28/2012 更新时间:10/1/2023 访问量:8574

问:

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

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

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

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

JavaScript的

评论


答:

53赞 T.J. Crowder 2/28/2012 #1

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

是的。从 ES2015 开始,由分配给对象属性的匿名函数表达式创建的函数采用该对象属性的名称。这是在所有现代浏览器中实现的,尽管 Edge 和 Safari 在堆栈跟踪中不使用该名称。我们可以将其与另一个 ES2015 功能(计算属性名称)结合使用,以命名一个不带 or 的函数。new Functioneval

在 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);
}

它也可以与 ,不需要方法语法,函数语法很好。如果你想以这种方式创建一个构造函数,这很方便:[dynamicName]: function() { }

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

当然,这是 ES2015+,所以你也可以用它来创建一个构造函数:class[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


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

不。如果没有构造函数或其表亲构造函数,就无法做到这一点。您的选择是:evalFunction

  1. 改用匿名函数。现代引擎会做一些事情来帮助调试这些。

  2. 用。eval

  3. 使用构造函数。Function

详:

  1. 改用匿名函数。如果你有一个漂亮的、明确的表达式(显示变量的名称),许多现代引擎都会显示一个有用的名称(例如,在调用堆栈等中),即使从技术上讲,该函数没有名称。(在 ES2015+ 中,以这种方式创建的函数实际上会有名称。但是,无论哪种方式,如果你想要一个真正的运行时定义的名称(来自变量的名称),你几乎被卡住了。var name = function() { ... };

  2. 用。 当你可以避免它时,它是邪恶的,但是对于你完全控制的字符串,在你控制的范围内,了解成本(你正在启动一个 JavaScript 解析器),做一些你不能做的事情(就像在这种情况下一样),只要你真的需要做这件事,这很好。但是,如果你无法控制字符串或作用域,或者你不想要成本,你将不得不忍受一个匿名函数。evaleval

    该选项的外观如下:eval

     // Remember, this is old-style ES5 code
     var name = "foo" + Math.floor(Math.random() * 1000);
     var f = eval(
         "(function() {\n" +
         "    function " + name + "() {\n" +
         "        console.log('Hi');\n" +
         "    }\n" +
         "    return " + name + ";\n" +
         "})();"
     );
     console.log(f.name);
     f();
    

    这将创建一个具有我们在运行时提供的名称的函数,而不会将该名称泄漏到包含的作用域中,将对该函数的引用分配给 .(而且它很好地格式化了代码,因此在调试器中单步执行代码很容易。f

    这在旧版本的Firefox中并没有正确地分配名称(令人惊讶)。从Firefox 29中JavaScript引擎的当前版本开始,它确实如此。

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

     // Remember, this is old-style ES5 code
     (function() {
         function display(msg) {
             console.log(msg);
         }
    
         var name = "foo" + Math.floor(Math.random() * 1000);
         var f = eval(
             "(function() {\n" +
             "   function " + name + "() {\n" +
             "       display('Hi');\n" +         // <=== Uses the
             "   }\n" +                          //      function above
             "   return " + name + ";\n" +
             "})();"
         );
         console.log(f.name);
         f();
     })();
     console.log(typeof display); // undefined (because it's not a global)
    
  3. 使用构造函数,如 Marcos Cáceres 的这篇文章中所示:Function

     // Remember, this is old-style ES5 code
     var name = "foo" + Math.floor(Math.random() * 1000);
     var f = new Function(
         "return function " + name + "() {\n" +
         "    console.log('Hi!');\n" +
         "};"
     )();
     console.log(f.name);
     f();
    

    在那里,我们创建一个临时匿名函数(通过构造函数创建的函数)并调用它;该临时匿名函数使用命名函数表达式创建命名函数。Function

    这比版本短,并且通过构造函数创建的函数权访问创建它们的范围。因此,上面的示例将失败,因为不会在创建的函数的范围内。因此,对于避免全局符号的整洁编码人员来说,这不是一个选项,但在您想要将生成的函数与生成函数的范围取消关联时很有用。evalFunctiondisplaydisplay

评论

2赞 PiTheNumber 2/28/2012
嗯,我明白你的意思了。好吧,我会考虑避免匿名函数。谢谢!
1赞 T.J. Crowder 7/30/2013
@Mike'Pomax'Kamermans:这有两个问题。:-)1. 你不能给函数一个真实的名字,和 2.您可以通过将 包装在块中来捕获语法错误,就像使用 .在这方面没有区别。new Functionevaltry/catchnew Function
1赞 T.J. Crowder 6/4/2014
@DhruvPathak:这在当时是一篇非常非常有用的文章。它已经过时了好几年了。唯一剩下的重大 NFE 问题是在 IE8 中;所有其他引擎(包括 IE9+)都正确无误。更多 Double-take
1赞 Bergi 5/19/2015
哦,计算属性的好用法:-)我很想在单独的答案中看到这一点,这样我就可以单独投票......
1赞 John Dvorak 7/26/2015
for 和 .我建议不要使用多行字符串。我还看到它被用于用户脚本中,该脚本通过创建新的脚本元素将自身注入页面。evalnew Functionfunction replaceHere(){...}.toString()
10赞 Bergi 6/4/2014 #2

这是我前段时间想出的一个实用函数。它使用了 @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赞 aljgom 2/3/2021 #3

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

将完成与公认的答案相同的结果

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

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

评论

0赞 Shenme 2/4/2021
在节点 (15) 下,我看到“[功能:新名称]”和“新名称”使用 .现在使用仍然打印出原始源“function test(){}”,但这是意料之中的。你的答案很好。console.logconsole.log('%s', f)