函数声明与函数表达式
函数定义几乎是我们做的最多的一件事了,有时候我们以 function a(){}
来申明,有时候以 var a = function(){}
来申明,他们是不是一样呢?
比如我在写代码的过程中,经常写下如下代码,但其实就在刚刚,我才似乎终于懂得了一些下面的函数定义到底发生了什么(附注释):
1 | (function () { // 函数表达式 |
函数声明
简单地说,我们直接在顶部,或函数内部直接定义的函数代码块就是函数声明:
1 | function func () { |
他有如下约定:
- 形如
function <funcName> () {}
, 里面的四个元素缺一不可 - 不属于表达式的一部分
第2点可能有些混淆,什么是表达式的一部分呢?我们以promise的调用为例:
1 | var promise = new Promise(...) |
可以看到在第3行和第5行中,我们定义的函数方式与函数声明的要求一致,然而这里的函数定义就是做为表达式的一部分而存在的,因此不是函数定义。这里的表示式是什么概念呢?我们稍后会提到。
函数声明会直接申明一个可调用的函数,其函数名就是申明时的函数名。同时函数声明具有变量提升的效果,故而我们可以在函数声明的前面,直接调用该函数变量。函数声明还是比较单纯的。
函数表达式
在周爱民老师的《JAVASCRIPT语言精髓与编程实践》一书中,这样定义“表达式”和“程序”:
表达式:有运算符和操作符构成,并产生运算结果的语法结构。比如1+2+3
就是一个表达式,他会返回6。
程序:程序是由语句构成的,语句则是由“;(分号,有时候可以省略)”分隔的句子或命令。在表达式后面加上”;”分隔符,则该表达式就是表达式语句。
简单地说,var func = function () {}
就是一个最常见的函数表达式,他与函数声明有如下区别:
- 他是整个“赋值表达式”一部分,而不是独立存在的。比如示例中,该function 及后面的语句,整体都是赋值运算符
=
中的值,完成这整个赋值运算符的整个过程就是”赋值表达式“在发挥作用 - 他可以省略函数名,比如创建匿名函数
还记得我们前面Promise例子中的
function success(){}
及function failed(){}
吗?我们在这里的定义中也为他们加上了函数名 success 和 failed, 但是他们的实际意义其实就是在源码中给我们看的。。从JS效果上来说,他们与匿名函数没有任何区别,其函数名其实会被直接忽略。我们可以通过一个简单的例子求证:
1 | var func1 = function func () {console.info('Here is the func body')} |
也就是说,我们虽然在定义时申明了 func 的变量,但是执行后并没有任何效果,其值被忽略,仍然被当做了匿名函数。
有趣的函数调用
无论是函数声明还是函数表达式,他们产生的函数都是用来被调用的,而且我们有多种调用函数的方式,如下:
下面主要内容来自周爱民老师的《JAVASCRIPT语言精髓与编程实践》一书,此书和常见的JavaScript书籍略有不同,作者以自己的方式讲述了JavaScript语句背后的原理,很值得一读!
1 | // 示例1 函数声明 |
示例1和示例2都是我们最常用的函数定义和调用方式,就不再细叙。我就按照书本里的内容直白地解释一下示例3、4、5。
在这之前,我们需要知道:
()
当不用做函数调用时,也具有强制语句执行的效果,也就是可以把它包裹的内容当做表达式来执行。他也是一种运算符,书里称之为“优先级运算符”。
示例3
通过最外面的 ()
强制执行了一次函数调用运算,该运算同时也执行了匿名函数(函数表达式)的定义,返回函数引用,故而后面的函数调用得以成功。
示例4
首先借助 ()
使得匿名函数(函数表达式)执行,由于该表达式是括号内唯一的也是最后的一个表达式,因此其值得以返回(即匿名函数的引用), 然后后面的 ()
作为函数调用的语法,通过引用调用了该匿名函数。
示例5
void 是JavaScript中特殊的一个运算符,他使得运算的对象返回undefined。例如
void 1;
也是合法的语句。作为运算符,他会使运算对象参与运算。
运算符需要针对值进行运算,因此JS引擎会把后面的语句当做表达式执行以获取值。将其执行的结果作为 void 的参数,void运算结束后,会返回 undefined。因此从某种角度来说,示例5和示例3类似,只是他们触发表达式执行的关键字不一样罢了。
在示例3、4、5我们总是不停地重复一个名词:表达式得以执行。 其实也就是使得里面的函数定义被当做”函数表达式“,并且得以执行,然后返回其引用。
为什么特别提到”使得里面的函数定义被当做函数表达式“呢?我们知道”函数表达式“和”函数声明“之间的区别,尤其是在都具备了函数名的时候,其实是取决于其是不是属于整个表达式的一部分,故而,如下的代码会不会正常调用呢?
1 | function func(name) { |
答案是否定的,由于没有
()
或void
的运算符,代码中的 function 定义部分,会被解析成函数声明,而不会返回引用;实际上解析后该代码块如下,函数声明和小括号后面都被加上了;
:
1 | // 解析成函数声明 |
如果在前面加上
void
关键字,那么就是我们期待的结果了,由于此时函数定义是表达式中的一部分,故没有被解析成函数声明。
总结
函数声明是一个很纯粹的函数声明方法,其函数名就是申明中的函数名,而且其申明必须是干干净净的。同时其申明具有变量提升的效果。
函数表达式, 比如匿名函数,或者任何作为表达式一部分的函数定义(此处特意与”函数声明“区别开来)
,都是函数表达式(好吧,其实是我目前自己这么理解的,还不能说得这么绝对==)。其被调用的方式,就是通过调用函数表达式的执行所返回的引用。
如果函数表达式中的函数设置了函数名,如 void function func (){}
。里面的函数名 func
, 其实只在源码中存在意义,编译后它没有意义,也不可以被引用到,即我们无法通过 func()
来调用该函数。