一尘不染

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

javascript

有什么方法可以创建一个在运行时确定的 真实名称
的函数,而无需使用eval,而仅使用纯JavaScript?(因此,不会生成任何script特定于浏览器环境的生成的元素(eval无论如何在很多方面都是伪装的;不会使用一个特定JavaScript引擎的非标准功能,等等)。

请注意,我特别 问有关的变量或有名称,如性质引用匿名函数:

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

那里,尽管对象属性有一个名称,但 函数
没有。匿名函数适用于很多事情,但不适用于我在这里寻找的东西。我希望函数具有名称(例如,显示在调试器的调用堆栈中,等等)。


阅读 203

收藏
2020-04-25

共1个答案

一尘不染

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

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

在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年开始)

不。没有构造函数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" +
    "})();"
);

这样就可以创建一个具有我们在运行时想出的名称的函数,而不会将该名称泄漏到包含的作用域中(也不会触发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" +
        "})();"
    );
})();
  1. 使用Function构造函数,如MarcosCáceres在本文中所演示的:
    var f = new Function(
    "return function " + name + "() {\n" +
    "    display('Hi!');\n" +
    "    debugger;\n" +
    "};"
    

    )();

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

它比eval版本短,但是有一个问题:通过Function构造函数创建的函数无法访问其创建范围。因此,上面的示例display将失败,因为它display不在所创建函数的作用域内。因此,不是让整洁的编码器避免使用全局符号的一种选择,但是对于那些希望将生成的函数与生成函数的范围解除关联的情况很有用。

2020-04-25