Function Declaration(函数声明)和函数表达式的区别

前言

在ECMAScript中,有两个最常用的创建函数对象的方法,即使用函数表达式或者使用函数声明。对此,ECMAScript规范明确了一点,即是,即函数声明 必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而函数表达式则可以省略。下面看看这两者的详细区别介绍。

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

Function Declaration 可以定义命名的函数变量,而无需给变量赋值。Function Declaration 是一种独立的结构,不能嵌套在非功能模块中。可以将它类比为 Variable Declaration(变量声明)。就像 Variable Declaration 必须以“var”开头一样,Function Declaration 必须以“function”开头。

举例来说

?


1

2

3

function bar() {

 return 3;

}

ECMA 5(13.0)定义语法:

function Identifier ( FormalParameterList[opt] ) { FunctionBody }

函数名在自身作用域和父作用域内是可获取的(否则就取不到函数了)。

?


1

2

3

4

5

6

function bar() {

 return 3;

}

 

bar() //3

bar //function

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

Function Expression 将函数定义为表达式语句(通常是变量赋值)的一部分。通过 Function Expression 定义的函数可以是命名的,也可以是匿名的。Function Expression 不能以“function”开头(下面自调用的例子要用括号将其括起来)。

举例来说

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

//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 的一种拓展,使得 Function Declaration 语句可以在任何允许使用 statement(语句)的地方使用。但是 Function Statement 现在还不是标准,所以不建议应用在产品开发中。

下面我们从一些小测试开始。猜猜以下情况都会弹出什么结果?

题 1:

?


1

2

3

4

5

6

7

8

9

10

function foo(){

 function bar() {

 return 3;

 }

 return bar();

 function bar() {

 return 8;

 }

}

alert(foo());

题 2:

?


1

2

3

4

5

6

7

8

9

10

function foo(){

 var bar = function() {

 return 3;

 };

 return bar();

 var bar = function() {

 return 8;

 };

}

alert(foo());

题 3:

?


1

2

3

4

5

6

7

8

9

10

alert(foo());

function foo(){

 var bar = function() {

 return 3;

 };

 return bar();

 var bar = function() {

 return 8;

 };

}

题 4:

?


1

2

3

4

5

6

7

8

9

10

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,也就是说它们 get hoisted(被提升)了……

等一下,什么是 Hoisting?

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

function declaration 被提升时,整个函数体都会随之提升,所以 Question 1 的代码经过解释器解释后是像这样运行的:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

//**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 中的第一个表达式:

?


1

2

3

var bar = function() {

 return 3;

};

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

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

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

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

//**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 在开始就被声明了,但是它的值并没有定义。其它代码都是按顺序执行的。

?


1

2

3

4

5

6

7

8

9

//**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 一样)。

?


1

2

3

4

5

6

7

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 看着像创建了对象。

?


1

2

3

4

//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(函数式编程)分不开。

?


1

2

//Function Expression

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

Function Expression 有缺点吗?

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

?


1

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

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

?


1

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

时间: 2024-10-18 13:35:52

Function Declaration(函数声明)和函数表达式的区别的相关文章

JavaScript 函数声明与函数表达式的区别 函数声明提升(function declaration hoisting)

解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁.解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问).至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真的被解释执行.例子: alert (sum(10,10)); function sum (num1, num2) { return num1 + num2; } 以上代码完全可以正常运行.因为在代码开始执行之前,解析器就已经通过一一个名为函数声明提升(function declaration hoist

Function类型-函数声明与函数表达式

实际上,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁.解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问):至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析执行.请看下面的例子: alert(sum(10,10)); function sum(num1,num2){ return num1+num2; } 以上代码完全可以正常运行.因为在代码执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoist

javaScript的函数(Function)对象的声明(@包括函数声明和函数表达式)

平时再用js写函数的时候,一般都是以惯例 function fn () {} 的方式来声明一个函数,在阅读一些优秀插件的时候又不免见到 var fn = function () {} 这种函数的创建,究竟他们用起来有什么区别呢,今天就本着打破砂锅问到底的精神,好好来说说这个让人神魂颠倒的--函数声明.  函数声明 函数声明示例代码 function fn () { console.log('fn 函数执行..'); // code.. } 这样我们就声明了一个名称为fn的函数,这里出个思考,你认

函数声明、函数表达式、匿名函数

函数声明.函数表达式.匿名函数 ()先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明 函数声明:function fnName () {-};使用function关键字声明一个函数,再指定一个函数名,叫函数声明. 函数表达式 var fnName = function () {-};使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式. 匿名函数:function ()

[转] 函数声明和函数表达式——函数声明的声明提前

定义函数的方法 定义函数的方法主要有三种: 函数声明(Function Declaration) 函数表达式Function Expression) new Function构造函数 其中,经常使用的是函数声明和函数表达式的函数定义方法,这两种方法有着很微妙的区别和联系,而且这两种方法的使用也容易混淆,所以这篇文章主要总结下这两种函数定义方法的相关知识点,当然本文的主题依然是关于函数提前的. 函数声明的典型格式: function functionName(arg1, arg2, ...){ <

函数声明和函数表达式——函数声明的声明提前

写在前面的那些不得不说的废话 前两天班级聚会,除了吃喝玩乐就是睡觉扯淡,甚是喜悦,真是独乐乐不如众乐乐啊. PS:毕业的或即将毕业的有时间能聚就聚吧,毕了业以后属于自己的时间能聚到一块儿可就少太多了. 现在有点时间来看点东西总结些东西了,又因为前段时间片片断断地看了看JavaScript的函数部分,所以抽时间总结下函数的相关部分,当然,里面有些部分都是自己的理解,如果有理解的不对的地方还请小伙伴们不吝指出. 这一节我结合自己的理解和小伙伴们聊一下函数声明的声明提前. 注:有的地方也叫函数声明提升

IE6 IE7 IE8 的函数声明和函数表达式的实现与其他浏览器有差异

标准参考 函数声明和函数表达式 定义一个函数有两种途径:函数声明和函数表达式. 函数声明: function Identifier ( FormalParameterList opt ) { FunctionBody } 函数表达式: function Identifier opt ( FormalParameterList opt ) { FunctionBody } ECMAScript 根据上下文来区分函数声明和函数表达式,假设 "function test(){}" 是一个表达

JavaScript函数声明与函数表达式

定义函数的方式有两种:一种是函数声明,另一种是函数表达式. 一.两者的语法 函数声明的语法: function functionName(arg0, arg1, arg2){ //函数体 } 函数表达式的语法: var functionName = function(arg0, arg1, arg2){ //函数体 } 因为function关键字后面没有标识符,这种情况下创建的函数叫做匿名函数(anonymous function,有时也叫拉姆达函数).匿名函数的name属性是空字符串. 二.两

JS函数声明和函数表达式的关系

1.函数声明 1 // 函数声明 2 function funDeclaration(type){ 3 return type==="Declaration"; 4 } 2.函数表达式 // 函数表达式 var funExpression = function(type){ return type==="Expression"; } JS代码在解析时会进行函数提升,因此在同一个作用域内,不管函数声明在哪里定义,该函数都可以进行调用.而函数表达式的值是在JS运行时确定

为什么if else 语句里不能用函数声明定义函数,而可以用函数表达式定义函数

在<JavaScript高级程序设计>第三版第7章函数表达式部分讲到,定义函数有两种方式:一种是函数声明,另一种就是函数表达式.函数声明的语法是这样的.function functionName(arg0, arg1, arg2) {//函数体} 关于函数声明,它的一个重要特征就是函数声明提升( function declaration hoisting),意思是在执行代码之前会先读取函数声明.这就意味着可以把函数声明放在调用它的语句后面.sayHi();function sayHi(){al