JavaScript中函数表达式和函数声明有什么区别?

511

以下代码有什么区别?

//Function declaration
function foo() { return 5; }

//Anonymous function expression
var foo = function() { return 5; }

//Named function expression
var foo = function foo() { return 5; }

问题:

  1. 什么是命名/匿名函数表达式?
  2. 什么是声明函数?
  3. 浏览器如何处理这些结构的不同之处?

类似问题的回答(var functionName = function() {} vs function functionName() {})没有完全正确地回答什么?


1
这是一篇关于命名函数表达式的好文章,第一部分讨论了函数表达式和声明的区别。文章链接:http://kangax.github.com/nfe。 - Evan Meagher
1
在我看来,主要区别在于提升(Hoisting)。以下是一篇关于这个的好文章:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html - Ryan Wheale
1
将链接修正为 有关命名函数表达式的好文章 - Rainald62
5个回答

430

它们实际上非常相似。你称呼它们的方式完全相同,区别在于浏览器将它们加载到执行上下文的方式不同。

函数声明在任何代码被执行之前就已经加载了。

函数表达式只有当解释器到达该行代码时才会加载。

因此,如果您尝试在加载函数表达式之前调用它,您将会得到一个错误!如果您调用函数声明,它将始终起作用,因为在所有声明被加载之前,没有任何代码可以被调用。

例如:函数表达式

alert(foo()); // ERROR! foo wasn't loaded yet
var foo = function() { return 5; } 

示例:函数声明

alert(foo()); // Alerts 5. Declarations are loaded before any code can run.
function foo() { return 5; } 

关于你问题的第二部分: var foo = function foo() { return 5; } 实际上与另外两个是一样的。只是这行代码在Safari浏览器中曾经会导致错误,但现在不会了。

34
最后一个例子与“var foo = function() { return 5; }”不同。因为这里的“foo.name”为空字符串,而在最后一个例子中它是“'foo'”。 - jonathancardoso
3
据我所知,name 属性不是 ECMAScript 的一部分,只有一些浏览器实现了它。详见 MDN 上的 Function.name - Zach Lysobey
8
@ZachL 只是作为例子,我想说的是第二个函数有一个名称,而第一个函数没有。 - jonathancardoso
3
那么,使用函数声明总是有效的。那么,使用函数表达式有什么好处呢?为什么不总是使用声明? - Richie Thomas
4
使用函数表达式被认为是最佳实践,因为这样行为比声明更直观。按照逻辑顺序来阅读更容易理解,你先定义它,然后调用它,如果不这样做,则会产生错误,这是预期的行为。实际上,我认为在非函数作用域中不允许使用函数声明...我建议您查看关于此主题的以下文章:http://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/ - Lior
显示剩余3条评论

113

函数声明

function foo() { ... }

由于函数提升,以这种方式声明的函数可以在定义之前和之后都被调用。

函数表达式

  1. 命名函数表达式

    var foo = function bar() { ... }
    
  2. 匿名函数表达式

  3. var foo = function() { ... }
    

foo() 只能在创建后调用。

立即调用的函数表达式(IIFE)

(function() { ... }());

结论

在他的书《JavaScript: The Good Parts》中,Douglas Crockford 建议使用函数表达式,因为这使得foo是一个包含函数值的变量,更加清晰明了。

个人而言,除非有特别的原因,我更喜欢使用声明方式。


10
欢迎来到Stack Overflow!感谢您的回答!请务必仔细阅读《自我推广常见问题解答》。同时,请注意,每次链接到您自己的网站/产品时,必须发布免责声明。 - Andrew Barber
1
注意点:JavaScript 是大小写敏感的。你的大写示例无法正常工作 ;-) - Zach Lysobey
1
另外,你也可以使用命名立即执行函数表达式:(function myFunc() { ... }()); - Zach Lysobey
1
更短、更常用的编写IIFE的方式:如果您不关心返回值,或者可能会使代码稍微难以阅读,您可以通过在函数前加上一元运算符来节省一个字节。例如: !function(){ /*code*/ }();(来源:链接文章 - naXa stands with Ukraine
function hoisting & variable hoisting & IIFE - xgqfrms
显示剩余2条评论

21

关于第三个定义:

var foo = function foo() { return 5; }

这里有一个示例,展示了如何使用递归调用的可能性:

a = function b(i) { 
  if (i>10) {
    return i;
  }
  else {
    return b(++i);
  }
}

console.log(a(5));  // outputs 11
console.log(a(10)); // outputs 11
console.log(a(11)); // outputs 11
console.log(a(15)); // outputs 15

编辑: 使用闭包的更有趣的例子:

a = function(c) {
 return function b(i){
  if (i>c) {
   return i;
  }
  return b(++i);
 }
}
d = a(5);
console.log(d(3)); // outputs 6
console.log(d(8)); // outputs 8

7
为了使函数成为递归函数,并不需要使用不同的名称进行声明。实际上,这样做可能会让事情变得更加混乱。使用 a = function a(i) 并执行 return a(++i) 的效果与声明不同名称的函数相同。 - PhilT
1
但是,如果函数使用不同的名称而不是变量名称,则更清楚地说明了这一点。为使用命名函数表达式提供示例而赞扬。 - gfullam

13
第一条语句的意义取决于它声明的上下文环境。
如果在全局范围内声明,它将创建一个隐含的全局变量"foo",该变量将指向这个函数。因此,在JavaScript程序的任何地方都可以调用函数"foo()"。
如果在闭包中创建函数,它将创建一个隐含的局部变量"foo",然后您可以使用它在闭包内调用该函数"foo()"
编辑:我还应该说一下函数语句(第一个)会在函数表达式(其他两个)之前解析。这意味着,如果您在脚本底部声明函数,则仍然可以在顶部使用它。只有当执行代码命中函数表达式时,函数表达式才会被评估。
句子2和3在很大程度上是相互等价的。同样,如果在全局环境中使用,它们将创建全局变量;如果在闭包中使用,则会创建局部变量。但值得注意的是,句子3将忽略函数名称,所以实际上您可以将该函数命名为任何名称。
var foo = function foo() { return 5; }

与之相同

var foo = function fooYou() { return 5; }

24
fooYou 不会被忽略。它在函数体中是可见的,因此函数可以引用自身(例如实现递归)。 - el.pescado - нет войне
2
这是一个很好的观点。我没有考虑到那个 :) - Alex
7
命名函数表达式对于调试很有用: var foo = function fooYou() { return 5; }; console.log(foo); console.log(foo.name); 将会打印出类似于 fooYou() / fooYou(在Firefox中), [Function: fooYou] / fooYou(在node.js中), function fooYou() { return 5; } / fooYou(在Chrome中)或者类似的内容,取决于你在哪里执行它。 - Gilead
命名函数表达式是推荐的做法,因为它允许您在需要时在内部引用该函数。例如,递归调用函数或处理其名称或属性。顺便说一下,主要好处是调试。如果使用未命名的函数,则很难调试发生在那里的任何事情,因为您将获得对匿名函数的引用而不是其名称。 - Antonio Pantano

1

虽然完整的差异更加复杂,但我关心的唯一差异是机器创建函数对象的时间。在声明的情况下,在执行任何语句之前但在调用语句体(无论是全局代码体还是子函数的)之后创建函数对象;而在表达式的情况下,则是在执行包含它的语句时创建函数对象。除此之外,对于所有目的,浏览器都将它们视为相同。

为了帮助您理解,请查看这个性能test,它打破了我对内部声明函数不需要在调用外部函数时重新创建的假设。有点可惜,因为我喜欢以那种方式编写代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接