在 JavaScript 中,函数表达式(Function Expression) 和 函数声明 (Function Declaration) 都可以用来定义函数,但它们有一些重要的区别:

语法区别
Function Declaration(函数声明):
function functionName() { // function body }
这是最常见的定义函数的方式,函数名称是显式指定的。作为独立语句在主代码流程中声明的函数。例如:
// Function Declaration function sum(a, b) { return a + b; }
Function Expression(函数表达式):
const functionName = function() { // function body };
这种方式通过将函数赋值给一个变量来定义函数,函数可以是匿名的(没有名字),也可以是具名的。在表达式内部或其他语法结构内部创建的函数。函数是在“赋值表达式”=`的右侧创建的,例如:
// Function Expression let sum = function(a, b) { return a + b; };
提升(Hoisting)
Function Declaration:函数声明会被提升到所在作用域的顶部,意味着可以在声明之前调用函数。
例如,全局函数声明在整个脚本中都是可见的,无论它位于何处。
这是由于内部算法的原因。当 JavaScript 准备运行脚本时,它首先会在其中寻找全局的函数声明,并创建这些函数。我们可以把它想象成一个“初始化阶段”。
在处理完所有函数声明之后,代码才开始执行。因此可以访问到这些函数。
sayHi("John"); // Hello, John function sayHi(name) { alert( `Hello, ${name}` ); }
函数声明 sayHi
是在 JavaScript 准备开始执行脚本时创建的,并且在整个脚本中都是可见的。
……如果它是一个函数表达式,那就不会生效了:
Function Expression:函数表达式不会被提升,只有在代码执行到那一行时,函数才会被定义。因此,必须先定义后调用。一旦执行流程到达赋值表达式右侧,例如 let sum = function…
—— 就在这里,函数被创建了,并且从此刻起可以使用(赋值、调用等)。
sayHi("John"); // error! let sayHi = function(name) { // (*) no magic any more alert( `Hello, ${name}` ); };
函数表达式是在执行流程运行到它们时才被创建的。这只会在标记为(*)的那一行发生。太晚了。
匿名 vs 命名
-
Function Declaration:函数是具名的,即函数的名称是显式声明的。
-
Function Expression:函数可以是匿名的(如上例),但也可以是具名的。
const greet = function greetFunction() { console.log("Hello"); };
块作用域(Block Scope)
Function Declaration:在函数声明的作用域内,函数会立刻可用(因为它是提升的),可以在声明之前引用。在严格模式下,当一个函数声明出现在代码块中时,它在整个块内部都是可见的,但在块外部不可见。
Function Expression:函数表达式中的函数只有在表达式执行后才有效,因此它会受限于其所在的作用域,必须在定义之后才能使用。
例如,假设我们需要根据在运行时获得的 age
变量来声明一个 welcome()
函数,然后稍后再使用它。
如果我们使用函数声明,那么它不会按预期工作:
let age = prompt("What is your age?", 18); // conditionally declare a function if (age < 18) { function welcome() { alert("Hello!"); } } else { function welcome() { alert("Greetings!"); } } // ...use it later welcome(); // Error: welcome is not defined
这是因为函数声明只在其所在的代码块内部可见。
这里有另一个例子:
let age = 16; // take 16 as an example if (age < 18) { welcome(); // \ (runs) // | function welcome() { // | alert("Hello!"); // | Function Declaration is available } // | everywhere in the block where it's declared // | welcome(); // / (runs) } else { function welcome() { alert("Greetings!"); } } // Here we're out of curly braces, // so we can not see Function Declarations made inside of them. welcome(); // Error: welcome is not defined
我们该如何让 welcome
在 if
外部也可见呢?
正确的方法是使用函数表达式(Function Expression),并将 welcome
赋值给一个在 if
外部声明、具有适当可见性的变量。
这段代码能按预期工作:
let age = prompt("What is your age?", 18); let welcome; if (age < 18) { welcome = function() { alert("Hello!"); }; } else { welcome = function() { alert("Greetings!"); }; } welcome(); // ok now
或者我们可以使用问号运算符 ?:
进一步简化它:
let age = prompt("What is your age?", 18); let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); }; welcome(); // ok now
用于回调函数和事件处理
-
Function Expression:通常用于回调函数或匿名函数的传递,因为它可以直接作为值传递给其他函数。
setTimeout(function() { console.log("This is a callback function."); }, 1000);
选择函数声明还是选择函数表达式?
通常来说,当我们需要声明一个函数时,首先应该考虑使用函数声明的语法。因为这样可以更自由地组织代码,我们可以在函数声明之前就调用它们。
这对于可读性也更好,因为在代码中查找 function f(…) {…}
要比 let f = function(…) {…};
更容易。函数声明更“醒目”。
……但是,如果由于某些原因函数声明不适合我们,或者需要条件式声明(我们刚刚看过一个例子),那么应该使用函数表达式。
总结
-
函数是值。它们可以在代码的任何位置被赋值、复制或声明。
-
如果函数作为主代码流程中的独立语句声明,那就是“函数声明”(Function Declaration)。
-
如果函数作为表达式的一部分创建,那就是“函数表达式”(Function Expression)。
-
函数声明在代码块执行之前会被处理,并且在整个代码块中都可见。
-
函数表达式在执行流程到达它们时才会被创建。
在大多数情况下,当我们需要声明一个函数时,函数声明是更可取的,因为它在声明之前就可见。这为代码组织提供了更多的灵活性,并且通常更具可读性。
因此,只有当函数声明不适合某个任务时,我们才应该使用函数表达式。在本章中我们已经看过一些例子,未来还会看到更多。