为什么要使用命名函数表达式?

IT技术 javascript function anonymous-function function-expression
2021-01-23 13:05:50

我们有两种不同的方式在 JavaScript 中进行函数表达式:

命名函数表达式(NFE)

var boo = function boo () {
  alert(1);
};

匿名函数表达式

var boo = function () {
  alert(1);
};

它们都可以用boo();. 我真的不明白为什么/何时应该使用匿名函数以及何时应该使用命名函数表达式。它们之间有什么区别?

5个回答

在匿名函数表达式的情况下,函数是匿名的 ——从字面上看,它没有名字。您分配给它的变量有一个名称,但函数没有。(更新:ES5 确实如此。从 ES2015 [aka ES6] 开始,通常使用匿名表达式创建的函数会获得真实名称 [但不是自动标识符],请继续阅读...)

名字很有用。名称可以在堆栈跟踪、调用堆栈、断点列表等中看到。名称是好东西™。

(你曾经不得不提防旧版本 IE [IE8 及以下] 中的命名函数表达式,因为它们错误地在两个完全不同的时间创建了两个完全独立的函数对象 [更多见我的博客文章Double take ]。如果你需要支持 IE8 [!!],最好坚持使用匿名函数表达式或函数声明,但避免命名函数表达式。)

命名函数表达式的一个关键是它为函数体内的函数创建了一个具有该名称的范围内标识符:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

然而,从 ES2015 开始,许多“匿名”函数表达式创建了带有名称的函数,而这早于各种现代 JavaScript 引擎非常聪明地从上下文推断名称。在 ES2015 中,匿名函数表达式会生成一个名为 的函数boo但是,即使使用 ES2015+ 语义,也不会创建自动标识符:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

函数名称的分配是通过在规范中的各种操作中使用SetFunctionName抽象操作完成的。

简短版本基本上是匿名函数表达式出现在诸如赋值或初始化之类的右侧的任何时候,例如:

var boo = function() { /*...*/ };

(或者它可能是letconst而不是var,或

var obj = {
    boo: function() { /*...*/ }
};

或者

doSomething({
    boo: function() { /*...*/ }
});

(最后两个实际上是一回事),结果函数将有一个名称(boo在示例中)。

有一个重要且有意的例外:分配给现有对象上的属性:

obj.boo = function() { /*...*/ }; // <== Does not get a name

这是因为在添加新功能的过程中引发了信息泄漏问题;在这里回答另一个问题的详细信息

值得注意的是,至少有两个地方使用 NFE 仍然具有具体的优势:首先,对于旨在通过new运算符用作构造函数的函数(给所有这些函数名称使该.constructor属性在调试期间更有用,以便弄清楚到底是什么某些对象是) 的实例,对于函数文字,直接传递给函数而不先分配给属性或变量(例如setTimeout(function () {/*do stuff*/});)。甚至 Chrome 也会显示这些,(anonymous function)除非您通过命名它们来帮助它。
2021-03-14 13:05:50
@MarkAmery:“这仍然是真的吗?我...尝试使用 CTRL-F 来查看这些规则,但找不到它们” 哦,是的。:-) 它散布在整个规范中,而不是在一个地方定义一组规则,只需搜索“setFunctionName”。我在上面添加了一小部分链接,但它目前出现在大约 29 个不同的地方。如果您的setTimeout示例没有从 for 声明的正式参数中获取名称,我只会感到有点惊讶setTimeout,如果它有的话。:-) 但是,是的,如果您知道自己不会处理对它们进行散列处理的旧浏览器,那么 NFE 绝对有用。
2021-03-23 13:05:50

如果需要引用自身(例如递归调用),命名函数很有用。事实上,如果你将一个文字函数表达式作为参数直接传递给另一个函数,那么该函数表达式不能在 ES5 严格模式下直接引用自身,除非它被命名。

例如,考虑以下代码:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

如果传递给的函数表达式setTimeout是匿名的,就不可能如此干净地编写此代码我们需要在setTimeout调用之前将它分配给一个变量这种方式,使用命名函数表达式,稍微更短更整洁。

从历史上看,即使使用匿名函数表达式也可以编写这样的代码,通过利用arguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

...但arguments.callee已弃用,并且在 ES5 严格模式中被完全禁止。因此 MDN 建议:

arguments.callee()通过为函数表达式指定名称或在函数必须调用自身的地方使用函数声明来避免使用

(强调我的)

您应该始终使用命名函数表达式,这就是为什么:

  1. 当您需要递归时,您可以使用该函数的名称。

  2. 匿名函数在调试时没有帮助,因为您看不到导致问题的函数的名称。

  3. 当你不命名一个函数时,以后就更难理解它在做什么。给它起个名字更容易理解。

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

例如,在这里,因为名称 bar 在函数表达式中使用,它不会在外部作用域中声明。对于命名函数表达式,函数表达式的名称包含在其自己的范围内。

如果将函数指定为函数表达式,则可以为其指定名称。

它只能在函数内部使用(IE8- 除外)。

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

此名称旨在用于可靠的递归函数调用,即使它被写入另一个变量。

此外,NFE(命名函数表达式)名称可以用Object.defineProperty(...)如下方法覆盖

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

注意:使用函数声明无法做到这一点。此“特殊”内部函数名称仅在函数表达式语法中指定。

当您希望能够引用相关函数而不必依赖诸如arguments.callee.

这更多的是评论而不是答案。也许详细说明会有益
2021-04-05 13:05:50