为何使用命名函数表达式?

时间:2013-03-11 10:31:38

标签: javascript function anonymous-function function-expression

我们在JavaScript中使用函数表达式有两种不同的方法:

命名函数表达式(NFE)

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

匿名函数表达式

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

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

5 个答案:

答案 0 :(得分:81)

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

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

(你曾经不得不提防旧版IE [IE8及以下]中的命名函数表达式,因为它们错误地在两个完全不同的时间创建了两个完全独立的函数对象[更多在我的博客文章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

这是因为当新功能正在进行添加过程时引发的信息泄漏问题;我对另一个问题here的回答中的详细信息。

答案 1 :(得分:22)

如果命名函数需要引用它们(例如,用于递归调用),则它很有用。实际上,如果您将一个文字函数表达式作为参数直接传递给另一个函数,那么该函数表达式不能直接在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建议:

  

避免使用{strong> 为函数表达式命名 使用arguments.callee(),或使用函数必须调用自身的函数声明。

(强调我的)

答案 2 :(得分:3)

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

它只能在函数内部使用(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();

注意:使用“功能声明”时,无法执行此操作。这&#34;特别&#34;内部函数名仅在函数表达式语法中指定。

答案 3 :(得分:1)

当您希望能够引用相关函数而不必依赖arguments.callee等弃用的功能时,使用命名函数表达式会更好。

答案 4 :(得分:1)

你应该总是使用命名的函数表达式,这就是原因:

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

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

  3. 如果您没有为某个功能命名,稍后就会更难理解它的功能。给它一个名字可以更容易理解。

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

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