JavaScript函数声明与函数表达式的核心区别及提升机制解析

作者:闭包旅人时间:2026-10-31 02:19:50浏览:566

本文详细解析JavaScript中函数声明与函数表达式的语法差异、提升行为及执行机制。通过ECMAScript规范与浏览器实际表现对比,阐明Function Declaration与Function Expression在变量提升、作用域及调试上的不同,并探讨命名函数表达式(NFE)的兼容性问题,帮助开发者写出更严谨的代码。

函数声明与表达式概述

前言

在ECMAScript标准中,创建函数对象主要有两种常用方式:函数声明(Function Declaration)和函数表达式(Function Expression)。ECMAScript规范明确规定,函数声明必须包含一个标识符(即函数名),而函数表达式则可以省略该标识符。下文将深入剖析这两者的具体区别。

什么是 Function Declaration(函数声明)?

Function Declaration 用于定义命名函数变量,无需显式赋值。它是一种独立的结构体,不能嵌套在非函数模块中。可以将其类比为变量声明(Variable Declaration):正如变量声明必须以“var”关键字开头,函数声明也必须以“function”关键字开头。

举例来说


function bar() {
 return 3;
}

ECMA 5(13.0)定义的语法如下:

function Identifier ( FormalParameterList[opt] ) { FunctionBody }

函数名在其自身作用域及父作用域中均可访问(若无法访问则意味着函数未被正确定义)。


function bar() {
 return 3;
}
 
bar() //3
bar //function

函数表达式的定义与特性

什么是 Function Expression(函数表达式)?

Function Expression 将函数定义为表达式语句(通常是变量赋值)的一部分。通过该方式定义的函数可以是命名的,也可以是匿名的。需要注意的是,Function Expression 不能以“function”关键字开头(例如在自调用示例中,需用括号将其包裹)。

举例来说


//anonymous function expression
var a = function() {
 return 3;
}
 
//named function expression
var a = function bar() {
 return 3;
}
 
//self invoking function expression
(function sayHello() {
 alert("hello!");
})();

ECMA 5(13.0)定义的语法如下:

function Identifieropt ( FormalParameterList[opt] ) { FunctionBody }

(该定义似乎不完整,因为它忽略了一个关键条件:外围语句必须是表达式,且不以“function”开头)。

函数名(如果存在)在作用域外部不可访问(这与 Function Declaration 形成对比)。

那 Function Statement 是什么?

Function Statement 有时被视为 Function Declaration 的同义词。但 kangax 指出,在 Mozilla 的实现中,Function Statement 是 Function Declaration 的一种扩展,允许函数声明在任何允许使用语句(statement)的地方出现。然而,Function Statement 目前并非标准,因此不建议在产品开发中使用。

代码测试与提升机制解析

下面我们通过几个小测试来开始。猜猜以下情况会弹出什么结果?

题 1:


function foo(){
 function bar() {
 return 3;
 }
 return bar();
 function bar() {
 return 8;
 }
}
alert(foo());

题 2:


function foo(){
 var bar = function() {
 return 3;
 };
 return bar();
 var bar = function() {
 return 8;
 };
}
alert(foo());

题 3:


alert(foo());
function foo(){
 var bar = function() {
 return 3;
 };
 return bar();
 var bar = function() {
 return 8;
 };
}

题 4:


function foo(){
 return bar();
 var bar = function() {
 return 3;
 };
 var bar = function() {
 return 8;
 };
}
alert(foo());

如果你的答案不是8、3、3和 [Type Error: bar is not a function],请继续阅读……(即使答对了也建议继续了解原理)。

现在来解释前面的测试。

Question 1 使用了 function declaration,这意味着它们被提升(hoisted)了……

等一下,什么是 Hoisting?

这里引用 Ben Cherry 的话:“Function declaration 和 function variable(函数变量)通常会被 JavaScript 解释器移(‘hoisted')到当前作用域顶部”。

当 function declaration 被提升时,整个函数体都会随之提升,因此 Question 1 的代码经过解释器处理后,运行逻辑如下:


//**Simulated processing sequence for Question 1**
function foo(){
 //define bar once
 function bar() {
 return 3;
 }
 //redefine it
 function bar() {
 return 8;
 }
 //return its invocation
 return bar(); //8
}
alert(foo());

但是,我们常被告知 return 语句后的代码是执行不到的啊……

在 JavaScript 执行过程中,存在 Context(ECMA 5 将其分解为 LexicalEnvironment、VariableEnvironment 和 ThisBinding)和 Process(一系列按序调用的语句)两个概念。当程序进入执行域时,Declaration 会创建 VariableEnvironment。它们不同于 Statement(如 return),也不遵循 Statement 的运行规则。

Function Expression 会被提升吗?

这取决于具体的表达式。例如 Question 2 中的第一个表达式:


var bar = function() {
 return 3;
};

等号左侧的(var bar)属于 Variable Declaration。Variable Declaration 会被提升,但 Assignment Expression(赋值表达式)不会。因此,当 bar 被提升时,解释器会这样初始化:var bar = undefined。而函数定义本身不会被提升。

(ECMA 5 12.2 规定,带有 initializer(初始化器)的变量是在 VariableStatement 执行时,由 AssignmentExpression 赋值的,而不是在变量被创建时。)

因此 Question 2 的代码会按以下顺序运行:


//**Simulated processing sequence for Question 2**
function foo(){
 //a declaration for each function expression
 var bar = undefined;
 var bar = undefined;
 //first Function Expression is executed
 bar = function() {
 return 3;
 };
 // Function created by first Function Expression is invoked
 return bar();
 // second Function Expression unreachable
}
alert(foo()); //3

你可能会说,这还能解释得通,但是 Question 3 的答案错了,我在 Firebug 运行会报错。

建议将代码保存在 HTML 文件中,然后在 Firefox 上运行试试。或者在 IE8、Chrome 或 Safari 控制台中运行。显然,Firebug 控制台在“global(全局)”作用域(实际上并非真正的全局,而是特有的“Firebug”作用域——试着在 Firebug 控制台中运行“this == window”即可验证)中运行代码时,不会执行函数提升。

Question 3 和 Question 1 的逻辑相似。这次是 foo 函数被提升了。

Question 4 就很简单了,根本就没有函数提升……

可以这么说,但如果根本没有提升,TypeError 应该是“bar not defined”,而不是“bar not a function”。此例中确实没有函数提升,但存在变量提升。因此 bar 在开始时就被声明了,但其值并未定义。其他代码都是按顺序执行的。


//**Simulated processing sequence for Question 4**
function foo(){
 //a declaration for each function expression
 var bar = undefined;
 var bar = undefined;
 return bar(); //TypeError: "bar not defined"
 //neither Function Expression is reached
}
alert(foo());

非标准使用与优缺点对比

还应该注意什么?

官方规范禁止在非功能模块(比如 if 语句块)中使用 Function Declaration。但是所有浏览器都支持,尽管各自的解释方式不同。

例如下面的代码段在 Firefox 3.6 中会抛错,因为它将 Function Declaration 解释成了 Function Statement(见上文),导致 x 没有定义。但是在 IE8、Chrome 5 和 Safari 5 中,会返回函数 x(和标准的 Function Declaration 一样)。


function foo() {
 if(false) {
 function x() {};
 }
 return x;
}
alert(foo());

可以看出使用 Function Declaration 可能会引起混淆,那么它有什么优点吗?

你可能会说 Function Declaration 很宽松——如果试图在声明前使用函数,提升确实可以修正顺序,以便函数可以正确调用。但是这种宽松不利于严谨的编码,从长远来看,很有可能会促进而不是阻止意外的发生。毕竟,程序员按特定的顺序排列语句是有原因的。

那么还有其它理由支持 Function Expression 的吗?

你猜呢?

1)Function Declaration 感觉像是要模仿 Java 风格的方法声明,但是 Java 方法和 JavaScript 并不一样。在 JavaScript 中,函数是含值的 living 对象。Java 方法仅是对元数据的存储。下面的两段代码都定义了函数,但是只有 Function Expression 看着像创建了对象。


//Function Declaration
function add(a,b) {return a + b};
//Function Expression
var add = function(a,b) {return a + b};

2)Function Expression 用处更多。Function Declaration 只能作为“statement”孤立存在。它所能做的就是创建一个当前作用域下的对象变量。相比之下,Function Expression(根据定义)是大型结构的一部分。如果想要创建匿名函数、给 prototype(原型)添加函数或是将函数用作其它对象的 property(属性),都可以用 Function Expression。每当用高阶应用,比如 curry 或 compose,创建新的函数时都是在用 Function Expression。Function Expression 和 Functional Programming(函数式编程)分不开。


//Function Expression
var sayHello = alert.curry("hello!");

Function Expression 有缺点吗?

Function Expression 创建的函数大多是匿名的。比如下面的函数是匿名的,today 只是一个匿名函数的引用:


var today = function() {return new Date()}

这会有问题吗?多数情况下不会,但是就像 Nick Fitzgerald 指出的,调试匿名函数会很烦。他建议使用 Named Function Expressions (NFEs)作为工作区:


var today = function today() {return new Date()}

但是如 Asen Bozhilov 所说(和 Kangax 文档)NFEs 在 IE9 以下无法正确执行。

最佳实践与结论

结论

随意放置的 Function Declaration 具有误导性,并且很少有(如果有的话)情况,用 Function Expression 给变量赋值无法替代 Function Declaration。但是如果必须使用 Function Declaration 的话,将其放在所属作用域顶部可以减少混淆。永远不要把 Function Declaration 放在 if 语句中。

说了这么多,可能在你自己的情况下,Function Declaration 还是很有用的。这没什么。死记教条是危险的,并且通常会造成代码拐弯抹角。更重要的是你理解了概念,这样就可以根据自身情况决定用哪种方式创建函数。以上就是本文的全部内容了,希望此文对大家在这方面有帮助。

作者最新文章