为什么这个问题连经验丰富的开发者都感到困惑?
\n如果你曾经写过 JavaScript,那么你一定遇到过这两种声明函数的方式。它们看起来相似,但行为却截然不同。在 Stack Overflow 上,这个问题获得了数千次浏览和数十页的讨论。为什么?因为不理解 var functionName = function() {} 和 function functionName() {} 之间的区别,会导致难以排查的错误。
今天,我们将一劳永逸地弄清楚这个主题。你将了解什么是 函数声明(function declaration) 和 函数表达式(function expression),变量提升(hoisting) 是如何工作的,以及何时使用哪种方式。我们开始吧!
\n\n语法背后隐藏着什么?
\n我们从基础开始。第一种方式是 函数声明(Function Declaration):
\nfunction greet() {\n console.log('你好!');\n}\n第二种是 函数表达式(Function Expression):
\nconst greet = function() {\n console.log('你好!');\n};\n请注意:在第一种情况下,函数有名称并且位于行首。在第二种情况下,我们将一个匿名函数赋值给变量 greet。但最有趣的部分在于 JavaScript 如何处理这些声明。
主要区别:变量提升(Hoisting)
\n变量提升(Hoisting) 是 JavaScript 的一种机制,它会在代码执行前将变量和函数的声明移动到其作用域的顶部。但它在函数声明和函数表达式中的工作方式不同。
\n\n函数声明(Function Declaration)会被整体提升
\n当你使用 function functionName() {} 时,JavaScript 会将整个函数声明“提升”到顶部。这意味着你可以在代码中声明函数之前调用它:
sayHello(); // "你好!"\n\nfunction sayHello() {\n console.log('你好!');\n}\n代码将无错误地执行。这很方便,但可能会让初学者感到困惑。
\n\n函数表达式(Function Expression)不会被提升
\n对于 var functionName = function() {} 来说,情况则不同。只有变量的声明会被提升,而它的值(函数)则不会。函数保留在原位:
sayHello(); // TypeError: sayHello is not a function\n\nvar sayHello = function() {\n console.log('你好!');\n};\n为什么会这样?因为 var 会提升变量 sayHello 的声明(其值为 undefined),而函数的赋值只会在表达式所在的行发生。因此,在这一行之前,sayHello 是 undefined,尝试将其作为函数调用会导致错误。
顺便提一下,如果你将 var 替换为 let 或 const,错误将会不同——ReferenceError: Cannot access 'sayHello' before initialization。这是因为 let 和 const 在声明之前不会被初始化(即所谓的“暂时性死区”)。
作用域:另一个容易出问题的地方
\n函数声明 和 函数表达式 都遵循作用域规则。但有一个细微差别。
\n\n函数声明(Function Declaration)在块级作用域内可见
\n尽管 函数声明 会被提升,但其作用域受限于块级作用域(在严格模式下):
\n'use strict';\nif (true) {\n function greet() {\n console.log('在块内部');\n }\n greet(); // "在块内部"\n}\ngreet(); // ReferenceError: greet is not defined(在严格模式下)\n在非严格模式下,这样的声明会“泄露”到外部,但不应依赖于此。
\n\n函数表达式(Function Expression)存在于其变量中
\n对于 函数表达式,一切都可以预测:函数仅在变量可访问的地方可用。如果移动