我们有两种不同的方式在 JavaScript 中进行函数表达式:
命名函数表达式(NFE):
var boo = function boo () {
alert(1);
};
匿名函数表达式:
var boo = function () {
alert(1);
};
并且它们都可以用 来调用boo();
。我真的不明白为什么/何时应该使用匿名函数以及何时应该使用命名函数表达式。他们之间有什么区别?
我们有两种不同的方式在 JavaScript 中进行函数表达式:
命名函数表达式(NFE):
var boo = function boo () {
alert(1);
};
匿名函数表达式:
var boo = function () {
alert(1);
};
并且它们都可以用 来调用boo();
。我真的不明白为什么/何时应该使用匿名函数以及何时应该使用命名函数表达式。他们之间有什么区别?
在匿名函数表达式的情况下,函数是匿名的 ——从字面上看,它没有名字。您分配给它的变量有一个名称,但函数没有。(更新:在 ES5 中确实如此。从 ES2015 [又名 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() { /*...*/ };
(或者它可能是let
或const
而不是var
),或
var obj = {
boo: function() { /*...*/ }
};
或者
doSomething({
boo: function() { /*...*/ }
});
(最后两个实际上是同一件事),生成的函数将有一个名称(boo
在示例中为 , )。
有一个重要且有意的例外:分配给现有对象的属性:
obj.boo = function() { /*...*/ }; // <== Does not get a name
这是因为在添加新功能的过程中会引发信息泄露问题;我在这里对另一个问题的回答中的详细信息。
如果函数需要引用自身(例如递归调用),命名函数很有用。实际上,如果您将文字函数表达式作为参数直接传递给另一个函数,则该函数表达式不能在 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()
通过给函数表达式命名或在函数必须调用自身的情况下使用函数声明来避免使用。
(强调我的)
您应该始终使用命名函数表达式,这就是为什么:
当您需要递归时,您可以使用该函数的名称。
匿名函数在调试时没有帮助,因为您看不到导致问题的函数的名称。
当您不命名函数时,以后很难理解它在做什么。给它起个名字更容易理解。
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
.