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


92

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

命名函数表达式(NFE)

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

匿名函数表达式

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

而且两个都可以用调用boo();。我真的看不到为什么/何时应该使用匿名函数以及何时应该使用命名函数表达式。他们之间有什么区别?


Answers:


85

对于匿名函数表达式,该函数是匿名的  -从字面上看,它没有名称。您要为其分配变量的变量具有名称,但该函数没有。(更新:这在ES5中是正确的。从ES2015 [aka ES6]开始,通常使用匿名表达式创建的函数会获得真实名称[但不是自动标识符],请继续阅读...)

名称很有用。可以在堆栈跟踪,调用堆栈,断点列表等中看到名称。名称是Good Thing™。

(您以前必须提防IE [IE8及以下版本的IE较旧版本中的命名函数表达式,因为它们在两个完全不同的时间错误地创建了两个完全独立的函数对象(请参阅我的博客文章Double take ]中的更多内容。)支持IE8 [!!],最好使用匿名函数表达式或函数声明,但要避免使用命名函数表达式。)

有关命名函数表达式的一个关键问题是,它会在functon体内使用该函数的名称创建一个范围内的标识符:

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

这是因为在添加新功能时提出了信息泄漏问题。我在这里回答另一个问题的细节。


1
值得注意的是,至少在两个地方使用NFE仍可带来具体优势:首先,对于打算通过new运算符用作构造函数的函数(给出所有此类函数名称会使该.constructor属性在调试过程中更加有用,以找出到底是什么问题)。某个对象是的实例),对于直接传递到函数中的函数文字,而无需先分配给属性或变量(例如setTimeout(function () {/*do stuff*/});)。甚至Chrome也会将这些内容显示为,(anonymous function)除非您通过命名来帮助它们。
Mark Amery

4
@MarkAmery:“这仍然是真的吗?我...尝试使用CTRL-F查找这些规则,但找不到它们。” 哦,是的。:-)它散布在整个规范中,而不是放在一个定义一组规则的地方,只需搜索“ setFunctionName”。我在上面添加了一小部分链接,但目前显示在大约29个不同的位置。如果您的setTimeout示例没有从为声明的形式参数中获取名称(如果有的话),我将感到非常惊讶setTimeout。:-)但是,是的,如果您不打算使用处理它们的旧浏览器,则NFE绝对有用。
TJ Crowder

24

如果命名功能需要引用自身(例如,用于递归调用),则命名功能很有用。实际上,如果您将文字函数表达式作为参数直接传递给另一个函数,则除非命名该函数表达式,否则无法在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()通过给函数表达式命名或在函数必须调用自身的地方使用函数声明来避免使用。

(强调我的)


3

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

它仅在函数内部可用(IE8-除外)。

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

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

该名称旨在用于可靠的递归函数调用,即使将其写入另一个变量也是如此。

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

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

test();

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


2

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

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

  2. 匿名函数在调试时无济于事,因为您看不到引起问题的函数名称。

  3. 当您不命名函数时,稍后将很难理解它的作用。给它起一个名字使它更容易理解。

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

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

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


1

如果您希望能够引用有问题的函数而不必依赖已弃用的功能(例如),则使用命名函数表达式会更好arguments.callee


3
那更多是评论而不是答案。详尽的说明可能会有所益处
vsync
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.